{
  "swagger": "2.0",
  "info": {
    "title": "Kelomo",
    "version": "1.0",
    "description": "Keep the Kelomo customer, project, task and tracking-target registers in step with the system that owns them, record and decide work days and absences, invite members, and react to what Kelomo does."
  },
  "host": "api.kelomo.fi",
  "basePath": "/",
  "schemes": ["https"],
  "consumes": ["application/json"],
  "produces": ["application/json"],
  "tags": [
    {
      "name": "Me",
      "description": "The authenticated member’s own data: overview, entitlements, work days, balances and requests. Reachable with ANY key — an empty-scope key is exactly this surface."
    },
    {
      "name": "Members",
      "description": "Members of the organisation (the workforce roster) and their full provisioning lifecycle: create (one or in bulk), invite, update, place in planning units, record the employment window and terms, terminate, deactivate and reactivate. The sensitive personal tier (identity number, bank account, home address), versioned payroll configuration and GDPR erasure are deliberately not reachable here."
    },
    {
      "name": "Search",
      "description": "One aggregate quick-search across every entity family the caller may read. Groups the caller has no access to come back empty, so the response shape leaks nothing."
    },
    {
      "name": "Work days",
      "description": "Work-time bookkeeping: work days, entries and their confirmation lifecycle — including the supervisor-side write that imports a workforce’s hours. Drafts are freely rewritten; a confirmed or approved day is never edited in place, only moved by a lifecycle action (reopen, return, correct)."
    },
    {
      "name": "Presence",
      "description": "Who is at work right now — punch-derived presence."
    },
    {
      "name": "Balances",
      "description": "Balance accounts (flex, overtime, banked leave) and their transactions."
    },
    {
      "name": "Shifts",
      "description": "Planned shifts, publish events, open shifts and shift swaps."
    },
    {
      "name": "Absences",
      "description": "Absence periods, policies and leave balances. Absence types are masked by sensitivity exactly as in the UI."
    },
    {
      "name": "Trips",
      "description": "Travel claims: the caller’s own trips, their travel days (per diem, night allowance, free meals) and the claim lifecycle. A trip is created as a draft and reaches an approver only when it is submitted. Requires the travel-and-expense module (402 `feature_not_in_plan` otherwise)."
    },
    {
      "name": "Expenses",
      "description": "Expense claims: the caller’s own receipt, allowance and mileage lines, the expense-type catalogue they are priced from, and the claim lifecycle. Approving them is on the Approvals surface. Requires the travel-and-expense module (402 `feature_not_in_plan` otherwise)."
    },
    {
      "name": "Geo",
      "description": "Geocoding and routing behind a mileage line or a day of visits: resolve a place to coordinates, and get the driving distance between them from the organisation’s configured provider. A lookup service, not a register — nothing is stored. Requires EITHER the travel-and-expense module or the field-service module (402 `feature_not_in_plan` when the organisation has neither)."
    },
    {
      "name": "Approvals",
      "description": "The unified approval queue: work days, absences, expenses and other pending decisions."
    },
    {
      "name": "Payroll exports",
      "description": "Payroll export batches: the frozen material handed to the payroll system."
    },
    {
      "name": "Billing exports",
      "description": "Billing export batches: invoice material handed to an external invoicing system."
    },
    {
      "name": "Invoices",
      "description": "Invoices and credit notes: drafting, issuing, dispatch (e-invoice/email) and renditions (PDF, Finvoice XML)."
    },
    {
      "name": "Payments",
      "description": "Accounts receivable: payments, allocations, bank imports (camt.054) and the AR aging view."
    },
    {
      "name": "Projects",
      "description": "Projects, phases, change orders, installments and purchases."
    },
    {
      "name": "Tasks",
      "description": "Work management tasks and their lifecycle."
    },
    {
      "name": "Quotes",
      "description": "Quotes: drafting, sending, customer decisions and conversion to projects."
    },
    {
      "name": "Customers",
      "description": "The customer register shared by CRM, projects and invoicing."
    },
    {
      "name": "CRM",
      "description": "Sales: leads, deals, pipelines, activities and the customer 360 view."
    },
    {
      "name": "Reports",
      "description": "Generated reports: a closed catalogue of report types rendered as JSON, PDF, XLSX or CSV."
    },
    {
      "name": "Catalogs",
      "description": "Tenant configuration catalogues: planning units, shift types, entry types, dimensions, staff groups, employers, leave groups and worktime models."
    },
    {
      "name": "Imports",
      "description": "Migration bulk imports: the admin-only data on-ramp. A batch always answers 200 with a per-row outcome, and re-running the same rows skips what already exists."
    },
    {
      "name": "Integration events",
      "description": "The tenant’s integration-event log: the same truth webhooks fan out, readable for reconciliation and polling."
    },
    {
      "name": "Webhooks",
      "description": "Outgoing webhook endpoints, delivery log and signing-secret management."
    },
    {
      "name": "API keys",
      "description": "API key management (human sessions only — a key cannot manage keys)."
    },
    {
      "name": "Audit",
      "description": "The administrative audit trail."
    },
    {
      "name": "Attachments",
      "description": "File attachments: presigned upload, finalize and download URLs."
    },
    {
      "name": "Intake",
      "description": "Inbound work requests (the front door for “create a request from a form”)."
    },
    {
      "name": "Field service",
      "description": "Kenttätyön ohjaus: work orders and the visits that fulfil them, the customer sites and the equipment they contain, maintenance agreements and the obligations they generate, and the signed work reports a visit produces. A work order carries its own customer and its own attribution value, so its hours, drives and parts flow through the same work-time and invoicing machinery as everything else. Requires the field-service module (402 `feature_not_in_plan` otherwise). Site ACCESS data (alarm codes, key-safe locations) is never reachable here, by design."
    },
    {
      "name": "Inventory",
      "description": "Parts stock behind field service, as a READ surface: the locations (a warehouse, a service van, consignment stock at a customer site) and the balances held in them — enough to name a `stockLocationId` in a published write and to see what it holds. The balance is a projection of an append-only movement ledger; the ledger itself, transfers between locations and stocktakes are not published. Requires the field-service module."
    }
  ],
  "paths": {
    "/v1/me/access": {
      "get": {
        "operationId": "AccessController_describe",
        "summary": "Check the connection",
        "description": "The member the call acts as, the capabilities it can actually exercise (the member’s effective set intersected with the key’s scope), the product modules in force at tenant and member scope, the key’s own declaration, and the rate limits in force. Compare `capabilities` with an operation’s `x-kelomo-capabilities` when a call is refused with `insufficient_role`, and `modules` with the operation’s 402 `feature_not_in_plan` when it is refused as unpurchased.\n\n**Capability:** none — the operation acts as the authenticated member and its authorisation is row-level (ownership, the approver line, project membership). An API key with an empty scope reaches it.",
        "tags": ["Me"],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/AccessContextDto"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/dimensions": {
      "get": {
        "operationId": "DimensionsController_list",
        "summary": "List tracking levels",
        "description": "**Capability:** none — the operation acts as the authenticated member and its authorisation is row-level (ownership, the approver line, project membership). An API key with an empty scope reaches it.",
        "tags": ["Catalogs"],
        "parameters": [
          {
            "name": "includeValues",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["true", "false"]
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/DimensionListResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/dimensions/{id}/values": {
      "get": {
        "operationId": "DimensionsController_listValues",
        "summary": "Find tracking targets",
        "description": "A cursor-paginated page of one tracking level's targets (`q` typeahead over code/name), or an exact lookup for an integration that holds a key: `code` inside the level, or `externalSource` + `externalId` (given together), or `ids`. Inactive targets are returned by every form — read `active`. Add `updatedSince` to read only what changed: inclusive (`updatedAt >= updatedSince`, de-duplicate by `id`), ordered `(updatedAt asc, id asc)`, every other filter still applies. Reachable by every key; the locked `project` level lists the projects register.\n\n**Capability:** none — the operation acts as the authenticated member and its authorisation is row-level (ownership, the approver line, project membership). An API key with an empty scope reaches it.",
        "tags": ["Catalogs"],
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "type": "string"
          },
          {
            "name": "cursor",
            "required": false,
            "in": "query",
            "type": "string"
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "minimum": 1,
            "maximum": 100,
            "default": 50,
            "type": "integer"
          },
          {
            "name": "updatedSince",
            "required": false,
            "in": "query",
            "x-minimum-year": 1900,
            "x-maximum-year": 2999,
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
            "type": "string"
          },
          {
            "name": "q",
            "required": false,
            "in": "query",
            "minLength": 1,
            "maxLength": 100,
            "type": "string"
          },
          {
            "name": "ids",
            "required": false,
            "in": "query",
            "minLength": 1,
            "maxLength": 4000,
            "type": "string"
          },
          {
            "name": "code",
            "required": false,
            "in": "query",
            "minLength": 1,
            "maxLength": 50,
            "type": "string"
          },
          {
            "name": "externalSource",
            "required": false,
            "in": "query",
            "pattern": "^[a-z][a-z0-9_]{1,63}$",
            "type": "string"
          },
          {
            "name": "externalId",
            "required": false,
            "in": "query",
            "minLength": 1,
            "maxLength": 200,
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/DimensionValueListResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "404": {
            "description": "No such object in this organisation — the one the path names, or one the request body refers to by id (`…_not_found`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/imports/tracking-targets": {
      "post": {
        "operationId": "ImportsController_trackingTargets",
        "summary": "Create or update tracking targets",
        "description": "Creates or updates tracking targets of ONE level in bulk — the on-ramp for targets that are maintained in another system (a project tool, the accounting package's cost centres) or in a file. `level` is the tracking level's own `code`: unknown answers 404 `dimension_not_found`, the locked `project` level 422 `project_values_managed_in_projects` (its targets are the projects register) and a deactivated level 422 `dimension_inactive` — those refuse the whole request, everything else is a per-row outcome. Answers 200 with one `results` row per submitted row: `{index, status, id, code, field?, targetCode}` where `status` is `created`, `updated`, `unchanged` or `error`, `id` is the matched or created target (null only in a dry run's create), `code` is the machine-readable ERROR code (null otherwise) and `targetCode` the target's own code — the key the next step quotes it by. MATCHING runs on the external reference first and on the code second: `externalSource` + `externalId` (given together, else `external_ref_incomplete`) hits the target that reference names; otherwise the `code` is looked up inside the level, and a code whose target already carries ANOTHER reference is refused `external_ref_conflict` with nothing written. A code whose target carries no reference is updated by a row that carries none either, and adopted by a row that carries one only when the request sets `linkByCode: true` — otherwise `code_in_use_unlinked`. No match creates, and `name` is then required (`missing_required`). A missing field keeps the stored value, `name` and `active` are honoured both ways, and a row that would change nothing answers `unchanged` with its `id` and writes no audit line, so a sync that runs every ten minutes leaves no trail of its own. A reference match keeps the target's own `code`. `parentCode` names a parent inside the same level (null = root) and applies on CREATE: parents named by the same batch are written before their children whatever order the rows are in, an unknown parent is `parent_not_found`, and a different parent on an existing target is `parent_change_unsupported`. Re-sending the same rows is idempotent and two concurrent batches carrying one row create one target. Send `dryRun: true` for the same report without writing anything — a row that would be created answers `created` with `id: null` — and the response echoes `dryRun`. `filename` (optional) names the source file on the run’s audit line and in the import history. Max 500 rows per batch — chunk larger files.\n\n**Capability:** `org.admin` — an API key must carry this in its scope.",
        "tags": ["Imports"],
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/BulkUpsertTrackingTargetsDto"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/BulkUpsertTrackingTargetsResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "409": {
            "description": "Conflict with the object’s current state — an append-only boundary, a concurrent edit, or an idempotency-key collision (`idempotency_key_reused`, `idempotency_key_in_flight`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/customers": {
      "get": {
        "operationId": "CustomersController_list",
        "summary": "Find customers",
        "description": "Buyer billing identifiers are masked for manager-tier callers exactly as in the UI. Filter with `q` (name/code/city typeahead), or look ONE account up by key: `code`, or its external reference `externalSource` + `externalId` (given together, else `external_ref_incomplete`). An exact lookup returns the account even when it is deactivated — the caller holding the key reads `active` itself.\n\n**Capability:** `crm.use` or `invoicing.manage` or `projects.manage` or `crm.privacy` — an API key must carry one of these in its scope.",
        "tags": ["Customers"],
        "parameters": [
          {
            "name": "cursor",
            "required": false,
            "in": "query",
            "type": "string"
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "minimum": 1,
            "maximum": 100,
            "default": 50,
            "type": "integer"
          },
          {
            "name": "updatedSince",
            "required": false,
            "in": "query",
            "x-minimum-year": 1900,
            "x-maximum-year": 2999,
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
            "type": "string"
          },
          {
            "name": "q",
            "required": false,
            "in": "query",
            "description": "Case-insensitive substring match over name, code and city. Business id joins the match only for a caller who may also READ it (`invoicing.manage`), and contacts only for a caller who may read them (`crm.use`) — otherwise a field you cannot see would still be probeable through its hits.",
            "minLength": 1,
            "maxLength": 200,
            "type": "string"
          },
          {
            "name": "cf",
            "required": false,
            "in": "query",
            "description": "Custom-field equality filter, e.g. `cf[segment]=enterprise` (deepObject). Several pairs are ANDed. A pair naming an unknown key, or a value the field cannot hold, matches NOTHING rather than being ignored — a filter whose effect the caller cannot see is worse than an empty result.",
            "style": "deepObject",
            "additionalProperties": {
              "type": "string",
              "minLength": 1,
              "maxLength": 1000
            },
            "type": "object"
          },
          {
            "name": "ownerMembershipId",
            "required": false,
            "in": "query",
            "description": "Return only accounts owned by this membership (asiakasvastaava).",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "type": "string"
          },
          {
            "name": "tagId",
            "required": false,
            "in": "query",
            "description": "Return only accounts carrying this CRM tag.",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "type": "string"
          },
          {
            "name": "sort",
            "required": false,
            "in": "query",
            "description": "Row order: `name` (default, the register’s alphabet), `code` (the account number), `openPipelineCents` or `lastActivityAt`. The two aggregate columns order accounts with nothing in play LAST in both directions. Ignored under `updatedSince`.",
            "type": "string",
            "enum": ["name", "code", "openPipelineCents", "lastActivityAt"]
          },
          {
            "name": "direction",
            "required": false,
            "in": "query",
            "description": "Sort direction. Defaults to `asc` for `name` and `code` and `desc` for the aggregate columns — the reading each is asked for first.",
            "type": "string",
            "enum": ["asc", "desc"]
          },
          {
            "name": "includeInactive",
            "required": false,
            "in": "query",
            "description": "Include deactivated accounts (`active: false`), which are hidden by default. ORDINARY LISTING ONLY: an `updatedSince` sweep already spans deactivated accounts, because deactivation is this register’s substitute for deletion and a mirror has to see it.",
            "type": "string",
            "enum": ["true", "false"]
          },
          {
            "name": "unowned",
            "required": false,
            "in": "query",
            "description": "Return only accounts with no customer owner (the register's \"Ilman vastaavaa\" queue).",
            "type": "string",
            "enum": ["true", "false"]
          },
          {
            "name": "idleDays",
            "required": false,
            "in": "query",
            "description": "Return only accounts with no completed activity in the last N days (an account with no activity at all counts).",
            "maximum": 3650,
            "exclusiveMinimum": true,
            "type": "integer",
            "minimum": 0
          },
          {
            "name": "code",
            "required": false,
            "in": "query",
            "description": "Exact match on the account’s tenant-unique code. Returns the row even when it is deactivated — a caller holding the key reads `active` itself.",
            "pattern": "^[a-z0-9][a-z0-9_-]{0,49}$",
            "type": "string"
          },
          {
            "name": "externalSource",
            "required": false,
            "in": "query",
            "description": "Exact match on the source namespace of an imported account; must be given together with `externalId` (`external_ref_incomplete`).",
            "pattern": "^[a-z][a-z0-9_]{1,63}$",
            "type": "string"
          },
          {
            "name": "externalId",
            "required": false,
            "in": "query",
            "description": "Exact match on the source system’s own key; must be given together with `externalSource`.",
            "minLength": 1,
            "maxLength": 200,
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/CustomerPageResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "402": {
            "description": "The organisation’s plan does not include the module this operation belongs to (`feature_not_in_plan`, with the missing `feature` named in the body). Not a permission problem — an administrator activates the module in Kelomo.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/customers/{id}": {
      "get": {
        "operationId": "CustomersController_get",
        "summary": "Get one customer",
        "description": "Buyer billing identifiers are masked for manager-tier callers exactly as in the UI.\n\n**Capability:** `crm.use` or `invoicing.manage` or `projects.manage` or `crm.privacy` — an API key must carry one of these in its scope.",
        "tags": ["Customers"],
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/CustomerGetResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "402": {
            "description": "The organisation’s plan does not include the module this operation belongs to (`feature_not_in_plan`, with the missing `feature` named in the body). Not a permission problem — an administrator activates the module in Kelomo.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "404": {
            "description": "No such object in this organisation — the one the path names, or one the request body refers to by id (`…_not_found`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/imports/customers": {
      "post": {
        "operationId": "ImportsController_customers",
        "summary": "Create or update customers",
        "description": "Answers 200 with one `results` row per submitted row — `{index, status, id, code, targetCode?}` where `index` is the 0-based position in the request, `status` is `created`, `skipped` or `error`, `id` is the created customer (null otherwise), `code` a machine-readable reason and `targetCode` the account’s own code. A partial import is a report, not an HTTP failure. Idempotent, re-running the same file creates nothing twice: a row whose `code` already exists is skipped (`code_already_in_use`), a row whose external reference already names an account is skipped (`external_ref_in_use`), and a row WITHOUT `code` whose company name or business id already names an active customer is skipped (`duplicate_customer`). Omit `code` and it is derived from the name, kept unique. A row that carries a NEW `code` for a company already in the register is still created — the register allows two accounts for one legal entity — and returns `status: created` with a warning code beside its `id` naming the key that matched — `resembles_existing_customer_business_id` or `resembles_existing_customer_name` (a WARNING and a SKIP are not the same fact, so they are not the same code; and the migrator has to know which column to go and compare). A created row whose value the register doubts but accepted — a business id that fails the Finnish check digit — also carries `warnings: [{field, code}]`. Send `upsert: true` to SYNC instead of migrate: a matched account is then UPDATED in place and `status` also takes `updated` and `unchanged`. The match runs on the external reference first — `externalSource` + `externalId`, given together (else `external_ref_incomplete`) — and on the `code` second; a code whose account already carries ANOTHER reference is refused `external_ref_conflict` with nothing written, and a code whose account carries none is adopted by a row carrying one only under `linkByCode: true`, otherwise `code_in_use_unlinked`. A row with neither key is `missing_match_key`. A missing field keeps the stored value, `active` is honoured both ways (false retires the account, true brings it back), and a row that would change nothing answers `unchanged` with its `id` and writes no audit line. Send `dryRun: true` for the same report without writing anything; the response echoes `dryRun`. `filename` (optional) names the source file on the run’s audit line and in the import history. Max 500 rows per batch — chunk larger files.\n\n**Capability:** `org.admin` — an API key must carry this in its scope.",
        "tags": ["Imports"],
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/BulkCreateCustomersDto"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/BulkImportResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "402": {
            "description": "The organisation’s plan does not include the module this operation belongs to (`feature_not_in_plan`, with the missing `feature` named in the body). Not a permission problem — an administrator activates the module in Kelomo.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "409": {
            "description": "Conflict with the object’s current state — an append-only boundary, a concurrent edit, or an idempotency-key collision (`idempotency_key_reused`, `idempotency_key_in_flight`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/projects": {
      "get": {
        "operationId": "ProjectsController_list",
        "summary": "Find projects",
        "description": "The project register as a cursor-paginated list, ordered by project code. Filter with `customerId`, `id` or `q` (code/name typeahead), or look ONE project up by key: `code`, or its external reference `externalSource` + `externalId` (given together, else `external_ref_incomplete`). Pass `updatedSince` for an incremental sweep: the page then carries only projects changed at or after that instant, ordered by `updatedAt` — resume from the largest `updatedAt` you have stored and de-duplicate by `id`. Deleted projects never appear here; read them from `GET /v1/integration-events`.\n\n**Capability:** `projects.read` or `invoicing.manage` — an API key must carry one of these in its scope.",
        "tags": ["Projects"],
        "parameters": [
          {
            "name": "cursor",
            "required": false,
            "in": "query",
            "type": "string"
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "minimum": 1,
            "maximum": 100,
            "default": 50,
            "type": "integer"
          },
          {
            "name": "updatedSince",
            "required": false,
            "in": "query",
            "x-minimum-year": 1900,
            "x-maximum-year": 2999,
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
            "type": "string"
          },
          {
            "name": "customerId",
            "required": false,
            "in": "query",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "type": "string"
          },
          {
            "name": "id",
            "required": false,
            "in": "query",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "type": "string"
          },
          {
            "name": "q",
            "required": false,
            "in": "query",
            "maxLength": 200,
            "type": "string"
          },
          {
            "name": "code",
            "required": false,
            "in": "query",
            "pattern": "^[a-z0-9][a-z0-9_-]{0,49}$",
            "type": "string"
          },
          {
            "name": "externalSource",
            "required": false,
            "in": "query",
            "pattern": "^[a-z][a-z0-9_]{1,63}$",
            "type": "string"
          },
          {
            "name": "externalId",
            "required": false,
            "in": "query",
            "minLength": 1,
            "maxLength": 200,
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/ProjectListResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "402": {
            "description": "The organisation’s plan does not include the module this operation belongs to (`feature_not_in_plan`, with the missing `feature` named in the body). Not a permission problem — an administrator activates the module in Kelomo.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/imports/projects": {
      "post": {
        "operationId": "ImportsController_projects",
        "summary": "Create or update projects",
        "description": "Answers 200 with one `results` row per submitted row — `{index, status, id, code, targetCode?}` where `index` is the 0-based position in the request, `status` is `created`, `skipped` or `error`, `id` is the created project (null otherwise), `code` a machine-readable reason and `targetCode` the project’s own code. A partial import is a report, not an HTTP failure. Idempotent: a row whose `code` already exists is skipped (`code_already_in_use`) and a row whose external reference already names a project is skipped (`external_ref_in_use`), so re-running the same file creates nothing twice — a row WITHOUT `code` gets one derived from the name; when the same customer already has a project of that name (case- and whitespace-insensitive, or an earlier row of the batch does) the row is still created but carries `code: resembles_existing_project_name`, and under `skipResembling: true` it is skipped instead (`duplicate_project_name`). The customer is resolved by `customerCode` first, then by exact name among ACTIVE customers only; a name shared by two of them is answered `customer_ambiguous` rather than guessed at. A project whose lifecycle patch failed after it was created returns `status: created` with `code: status_not_applied` — the project exists, its status was not applied, and re-running the row would duplicate it. Send `upsert: true` to SYNC instead of migrate: a matched project is then UPDATED in place and `status` also takes `updated` and `unchanged`. The match runs on the external reference first — `externalSource` + `externalId`, given together (else `external_ref_incomplete`) — and on the `code` second; a code whose project already carries ANOTHER reference is refused `external_ref_conflict` with nothing written, and a code whose project carries none is adopted by a row carrying one only under `linkByCode: true`, otherwise `code_in_use_unlinked`. A row with neither key is `missing_match_key`. A missing field keeps the stored value, and the row’s `status` is the source’s close and reopen — applied through the project’s own lifecycle, so `closed` stamps the closure and `active` clears it. A row that would change nothing answers `unchanged`. Send `dryRun: true` for the same report without writing anything; the response echoes `dryRun`. `filename` (optional) names the source file on the run’s audit line and in the import history. Max 500 rows per batch — chunk larger files.\n\n**Capability:** `org.admin` — an API key must carry this in its scope.",
        "tags": ["Imports"],
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/BulkCreateProjectsDto"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/BulkImportResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "402": {
            "description": "The organisation’s plan does not include the module this operation belongs to (`feature_not_in_plan`, with the missing `feature` named in the body). Not a permission problem — an administrator activates the module in Kelomo.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "409": {
            "description": "Conflict with the object’s current state — an append-only boundary, a concurrent edit, or an idempotency-key collision (`idempotency_key_reused`, `idempotency_key_in_flight`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/projects/{id}/tasks/page": {
      "get": {
        "operationId": "ProjectTasksController_taskPage",
        "summary": "Find tasks in a project",
        "description": "Cursor-paginated tasks in one lens: one board column (statusId), the archive (archived=true), everything changed since an instant (updatedSince — the incremental-sync read, which spans archived tasks too), or an EXACT lookup. Exactly one of the first three must be selected unless the request is an exact lookup. An exact lookup answers the one task it names or nothing at all, and spans the archive: `externalSource` + `externalId` (given together, else 400 `external_ref_incomplete`) names the task by the source system's own identity, and `number` by the task's per-project running number — the half of its human key `<projectCode>-<number>` this route does not already stand inside. (`q` matches the number too, but as free text; an exact lookup is not a search.)\n\n**Capability:** `projects.read` — an API key must carry this in its scope.",
        "tags": ["Tasks"],
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "type": "string"
          },
          {
            "name": "cursor",
            "required": false,
            "in": "query",
            "type": "string"
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "minimum": 1,
            "maximum": 100,
            "default": 50,
            "type": "integer"
          },
          {
            "name": "statusId",
            "required": false,
            "in": "query",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "type": "string"
          },
          {
            "name": "assigneeMembershipId",
            "required": false,
            "in": "query",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "type": "string"
          },
          {
            "name": "phaseId",
            "required": false,
            "in": "query",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "type": "string"
          },
          {
            "name": "milestoneId",
            "required": false,
            "in": "query",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "type": "string"
          },
          {
            "name": "priority",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["low", "normal", "high", "urgent"]
          },
          {
            "name": "tagId",
            "required": false,
            "in": "query",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "type": "string"
          },
          {
            "name": "q",
            "required": false,
            "in": "query",
            "maxLength": 200,
            "type": "string"
          },
          {
            "name": "includeClosed",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["true", "false"]
          },
          {
            "name": "archived",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["true", "false"]
          },
          {
            "name": "fieldId",
            "required": false,
            "in": "query",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "type": "string"
          },
          {
            "name": "fieldValue",
            "required": false,
            "in": "query",
            "maxLength": 100,
            "type": "string"
          },
          {
            "name": "updatedSince",
            "required": false,
            "in": "query",
            "x-minimum-year": 1900,
            "x-maximum-year": 2999,
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
            "type": "string"
          },
          {
            "name": "externalSource",
            "required": false,
            "in": "query",
            "pattern": "^[a-z][a-z0-9_]{1,63}$",
            "type": "string"
          },
          {
            "name": "externalId",
            "required": false,
            "in": "query",
            "minLength": 1,
            "maxLength": 200,
            "type": "string"
          },
          {
            "name": "number",
            "required": false,
            "in": "query",
            "maximum": 2147483647,
            "exclusiveMinimum": true,
            "type": "integer",
            "minimum": 0
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/TaskPageResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "402": {
            "description": "The organisation’s plan does not include the module this operation belongs to (`feature_not_in_plan`, with the missing `feature` named in the body). Not a permission problem — an administrator activates the module in Kelomo.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "404": {
            "description": "No such object in this organisation — the one the path names, or one the request body refers to by id (`…_not_found`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/imports/tasks": {
      "post": {
        "operationId": "ImportsController_tasks",
        "summary": "Create or update tasks",
        "description": "Answers 200 with one `results` row per submitted row — `{index, status, id, code, field?, targetCode?}` where `index` is the 0-based position in the request, `status` is `created`, `updated`, `unchanged`, `skipped` or `error`, `id` is the matched or created task (null on an error or a skip), `code` the machine-readable reason (null otherwise, save the resemblance warning below) and `targetCode` the task's human key `<projectCode>-<number>` — what the next step quotes it by. A partial import is a report, not an HTTP failure. A row carrying NO `externalSource`/`externalId` is CREATED, exactly as before: a task has no tenant-unique key of its own, so re-sending such rows creates them again — and the created row carries `code: resembles_existing_task_title` when the project already has a task of that title (case- and whitespace-insensitive), while `skipResembling: true` answers `skipped` with `duplicate_task_title` instead. A row that DOES carry the pair (given together, else `external_ref_incomplete`) is a SYNC row: the reference is looked up first and a hit is updated in place, a hit in another project answers `task_project_change_unsupported` (the import never moves a task), and no hit creates — `title` is then required (`missing_required`). Send `linkByTitle: true` to adopt the task the same project already has under the row's exact title when the reference matches nothing yet: an unclaimed match is adopted and updated, a match another source already owns is `external_ref_conflict` with nothing written, and two matches are `task_title_ambiguous`. A missing field keeps the stored value, and a row that would change nothing answers `unchanged` with its `id`, writing neither an activity line nor a webhook — a sync that runs every ten minutes leaves no trail of its own. Send `dryRun: true` for the same report without writing anything — a row that would be created answers `created` with `id: null` and no `targetCode` — and the response echoes `dryRun`. `externalStatus` carries the source's own word for the state and is read through the tenant's status mappings into a Kelomo status category (unmapped: `status_mapping_missing`); it wins over `done`. Two concurrent batches carrying one row create one task. `filename` (optional) names the source file on the run’s audit line and in the import history. Max 500 rows per batch — chunk larger files.\n\n**Capability:** `org.admin` — an API key must carry this in its scope.",
        "tags": ["Imports"],
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/BulkCreateTasksDto"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/BulkImportResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "402": {
            "description": "The organisation’s plan does not include the module this operation belongs to (`feature_not_in_plan`, with the missing `feature` named in the body). Not a permission problem — an administrator activates the module in Kelomo.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "409": {
            "description": "Conflict with the object’s current state — an append-only boundary, a concurrent edit, or an idempotency-key collision (`idempotency_key_reused`, `idempotency_key_in_flight`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/me/work-days/{date}": {
      "get": {
        "operationId": "DaysController_getDay",
        "summary": "Get my work day",
        "description": "**Capability:** none — the operation acts as the authenticated member and its authorisation is row-level (ownership, the approver line, project membership). An API key with an empty scope reaches it.",
        "tags": ["Me"],
        "parameters": [
          {
            "name": "date",
            "required": true,
            "in": "path",
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/WorkDayDetailResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "404": {
            "description": "No such object in this organisation — the one the path names, or one the request body refers to by id (`…_not_found`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/me/work-days/{date}/entries": {
      "put": {
        "operationId": "DaysController_setEntries",
        "summary": "Replace my work day's entries",
        "description": "Full replacement of the day’s entries; idempotent. Draft days only — a confirmed day must be reopened first.\n\n**Capability:** none — the operation acts as the authenticated member and its authorisation is row-level (ownership, the approver line, project membership). An API key with an empty scope reaches it.",
        "tags": ["Me"],
        "parameters": [
          {
            "name": "date",
            "required": true,
            "in": "path",
            "type": "string"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/SetWorkEntriesDto"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/WorkDayDetailResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "404": {
            "description": "No such object in this organisation — the one the path names, or one the request body refers to by id (`…_not_found`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "409": {
            "description": "Conflict with the object’s current state — an append-only boundary, a concurrent edit, or an idempotency-key collision (`idempotency_key_reused`, `idempotency_key_in_flight`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/team/members/{membershipId}/work-days/{date}": {
      "get": {
        "operationId": "TeamDaysController_getDay",
        "summary": "Get a member's work day",
        "description": "**Capability:** `worktime.supervise` — an API key must carry this in its scope.",
        "tags": ["Work days"],
        "parameters": [
          {
            "name": "membershipId",
            "required": true,
            "in": "path",
            "type": "string"
          },
          {
            "name": "date",
            "required": true,
            "in": "path",
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/WorkDayDetailResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "404": {
            "description": "No such object in this organisation — the one the path names, or one the request body refers to by id (`…_not_found`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/team/members/{membershipId}/work-days/{date}/entries": {
      "put": {
        "operationId": "TeamDaysController_setEntries",
        "summary": "Replace a member's work day entries",
        "description": "THE time-import route: writes one member’s day from an external system (a terminal, another time tracker, a migration). The request REPLACES the day’s entries in full, so re-sending the same body is idempotent — a retried import cannot double-write and needs no Idempotency-Key. Draft days only: a confirmed, approved or exported day answers 409 day_confirmed_locked and must go back to draft through reopen (confirmed) or correct (approved/exported) first — a record past the confirmation boundary is never edited in place. Requires the organisation’s “supervisor may act on behalf” setting and authority over the member. The write does not judge the employer’s booker-data rules (required attribution, booking precision, entry maximum, manual-entry justification) — those are enforced at CONFIRMATION, so an import that passes here can still be refused there.\n\n**Capability:** `worktime.supervise` — an API key must carry this in its scope.",
        "tags": ["Work days"],
        "parameters": [
          {
            "name": "membershipId",
            "required": true,
            "in": "path",
            "type": "string"
          },
          {
            "name": "date",
            "required": true,
            "in": "path",
            "type": "string"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/SetWorkEntriesDto"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/WorkDayDetailResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "404": {
            "description": "No such object in this organisation — the one the path names, or one the request body refers to by id (`…_not_found`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "409": {
            "description": "Conflict with the object’s current state — an append-only boundary, a concurrent edit, or an idempotency-key collision (`idempotency_key_reused`, `idempotency_key_in_flight`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/entry-types": {
      "get": {
        "operationId": "EntryTypesController_list",
        "summary": "List entry types",
        "description": "**Capability:** none — the operation acts as the authenticated member and its authorisation is row-level (ownership, the approver line, project membership). An API key with an empty scope reaches it.",
        "tags": ["Catalogs"],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/EntryTypeListResponseDto"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/work-days/{id}/approve": {
      "post": {
        "operationId": "ConfirmationsController_approve",
        "summary": "Approve a work day",
        "description": "**Capability:** `worktime.supervise` — an API key must carry this in its scope.",
        "tags": ["Work days"],
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "type": "string"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/ApproveWorkDayDto"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created.",
            "schema": {
              "$ref": "#/definitions/WorkDayActionResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "404": {
            "description": "No such object in this organisation — the one the path names, or one the request body refers to by id (`…_not_found`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "409": {
            "description": "Conflict with the object’s current state — an append-only boundary, a concurrent edit, or an idempotency-key collision (`idempotency_key_reused`, `idempotency_key_in_flight`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/work-days/{id}/return": {
      "post": {
        "operationId": "ConfirmationsController_returnToDraft",
        "summary": "Return a work day",
        "description": "**Capability:** `worktime.supervise` — an API key must carry this in its scope.",
        "tags": ["Work days"],
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "type": "string"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/ReturnWorkDayDto"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created.",
            "schema": {
              "$ref": "#/definitions/WorkDayActionResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "404": {
            "description": "No such object in this organisation — the one the path names, or one the request body refers to by id (`…_not_found`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "409": {
            "description": "Conflict with the object’s current state — an append-only boundary, a concurrent edit, or an idempotency-key collision (`idempotency_key_reused`, `idempotency_key_in_flight`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/absence-periods/{id}/approve": {
      "post": {
        "operationId": "AbsenceController_approve",
        "summary": "Approve an absence",
        "description": "**Capability:** `worktime.supervise` or `hr.manage` — an API key must carry one of these in its scope.",
        "tags": ["Approvals"],
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "type": "string"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/ApproveAbsencePeriodDto"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created.",
            "schema": {
              "$ref": "#/definitions/AbsencePeriodDetailDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "404": {
            "description": "No such object in this organisation — the one the path names, or one the request body refers to by id (`…_not_found`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "409": {
            "description": "Conflict with the object’s current state — an append-only boundary, a concurrent edit, or an idempotency-key collision (`idempotency_key_reused`, `idempotency_key_in_flight`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/absence-periods/{id}/reject": {
      "post": {
        "operationId": "AbsenceController_reject",
        "summary": "Reject an absence",
        "description": "**Capability:** `worktime.supervise` or `hr.manage` — an API key must carry one of these in its scope.",
        "tags": ["Approvals"],
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "type": "string"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/RejectAbsencePeriodDto"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created.",
            "schema": {
              "$ref": "#/definitions/AbsencePeriodDetailDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "404": {
            "description": "No such object in this organisation — the one the path names, or one the request body refers to by id (`…_not_found`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "409": {
            "description": "Conflict with the object’s current state — an append-only boundary, a concurrent edit, or an idempotency-key collision (`idempotency_key_reused`, `idempotency_key_in_flight`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/memberships": {
      "get": {
        "operationId": "MembershipsController_list",
        "summary": "Find members",
        "description": "Scoped to the caller’s authority: a supervisor sees only their own reports.\n\n**Capability:** `members.manage` or `worktime.supervise` or `payroll.run` — an API key must carry one of these in its scope.",
        "tags": ["Members"],
        "parameters": [
          {
            "name": "cursor",
            "required": false,
            "in": "query",
            "type": "string"
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "minimum": 1,
            "maximum": 100,
            "default": 50,
            "type": "integer"
          },
          {
            "name": "updatedSince",
            "required": false,
            "in": "query",
            "x-minimum-year": 1900,
            "x-maximum-year": 2999,
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
            "type": "string"
          },
          {
            "name": "q",
            "required": false,
            "in": "query",
            "maxLength": 200,
            "type": "string"
          },
          {
            "name": "role",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["employee", "manager", "admin", "payroll", "hr"]
          },
          {
            "name": "unitId",
            "required": false,
            "in": "query",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "type": "string"
          },
          {
            "name": "employerId",
            "required": false,
            "in": "query",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "type": "string"
          },
          {
            "name": "leaveGroupId",
            "required": false,
            "in": "query",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "type": "string"
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["active", "future", "ended"]
          },
          {
            "name": "incompleteOnly",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["true", "false"]
          },
          {
            "name": "noSupervisor",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["true", "false"]
          },
          {
            "name": "includeInactive",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["true", "false"]
          },
          {
            "name": "leadsOnly",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["true", "false"]
          },
          {
            "name": "excludePayroll",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["true", "false"]
          },
          {
            "name": "attention",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["true", "false"]
          },
          {
            "name": "needsAttention",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["true", "false"]
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/MembershipListResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      },
      "post": {
        "operationId": "MembershipsController_create",
        "summary": "Invite a member",
        "description": "Required: `firstNames`, `lastName` and `role`. `preferredName` is optional, and the display name is derived from the parts (it is not accepted). `email` is optional — omit it for a device-only punch-clock worker, and the server mints a synthetic, never-deliverable address and sends no invite (give a `phone` so SMS can still reach them). Every optional id (`managerMembershipId`, `worktimeModelId`, `leaveGroupId`, `planningUnitId`) is readable from the public surface, so nothing here needs an internal endpoint. `planningUnitId` opens the person’s unit assignment in the same transaction; it is optional in general but REQUIRED — and limited to the caller’s own units — for a unit-scoped HR coordinator, who would otherwise lose the member they just created (`422 planning_unit_required`, `404 planning_unit_not_found`). Give `employmentStartDate` for a person whose employment began BEFORE this record: it is the floor every calendar and gap surface reads, and without it the expectation starts on the day the record was created (`employmentEndDate` bounds the other end). A new passwordless member is created AND invited in one call: `member.created` and `member.invited` both fire, and the response carries the one-time activation URL. Sending `initialPassword` instead creates a TEMPORARY credential: the member can sign in, but every other route answers `403 password_change_required` until they call `POST /v1/auth/change-password` themselves — so a roster provisioned this way is inert until each person has signed in once, and an admin can only hand out a new temporary password (`POST /v1/memberships/{id}/reset-password`), never clear the flag on their behalf. Called with an API key the `Idempotency-Key` header is REQUIRED — a retried create then replays the original 201 instead of answering `409 already_member` with no id. Re-creating an address that is already a member of this organisation is `409 already_member`.\n\n**Capability:** `members.manage` — an API key must carry this in its scope.",
        "tags": ["Members"],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Caller-generated unique key (max 200 characters) that makes this write exactly-once — a money document or a payroll/billing hand-over. Required for API-key and OAuth callers; deduplicated per organisation for 24 h. Replaying the same key with the same body replays the stored response; a different body is refused with 409 `idempotency_key_reused`.",
            "example": "payroll-run-2026-08-invoice-4711",
            "type": "string",
            "maxLength": 200
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/CreateMembershipDto"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created.",
            "schema": {
              "$ref": "#/definitions/CreateMembershipResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "404": {
            "description": "No such object in this organisation — the one the path names, or one the request body refers to by id (`…_not_found`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "409": {
            "description": "Conflict with the object’s current state — an append-only boundary, a concurrent edit, or an idempotency-key collision (`idempotency_key_reused`, `idempotency_key_in_flight`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/invoicing/invoices": {
      "get": {
        "operationId": "InvoicesController_list",
        "summary": "Find invoices",
        "description": "**Capability:** `projects.manage` or `invoicing.manage` — an API key must carry one of these in its scope.",
        "tags": ["Invoices"],
        "parameters": [
          {
            "name": "cursor",
            "required": false,
            "in": "query",
            "type": "string"
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "minimum": 1,
            "maximum": 100,
            "default": 50,
            "type": "integer"
          },
          {
            "name": "updatedSince",
            "required": false,
            "in": "query",
            "x-minimum-year": 1900,
            "x-maximum-year": 2999,
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
            "type": "string"
          },
          {
            "name": "customerId",
            "required": false,
            "in": "query",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "type": "string"
          },
          {
            "name": "projectId",
            "required": false,
            "in": "query",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "type": "string"
          },
          {
            "name": "q",
            "required": false,
            "in": "query",
            "maxLength": 200,
            "type": "string"
          },
          {
            "name": "lane",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": [
              "draft",
              "approved",
              "issued",
              "sent",
              "overdue",
              "paid",
              "written_off",
              "credited",
              "refund_due",
              "no_payment_due"
            ]
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["draft", "issued"]
          },
          {
            "name": "reviewStatus",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["none", "requested", "approved", "returned"]
          },
          {
            "name": "documentType",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["invoice", "credit_note", "reminder", "advance", "charge"]
          },
          {
            "name": "from",
            "required": false,
            "in": "query",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "type": "string"
          },
          {
            "name": "to",
            "required": false,
            "in": "query",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "type": "string"
          },
          {
            "name": "overdue",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["true", "false"]
          },
          {
            "name": "openOnly",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["true", "false"]
          },
          {
            "name": "unsent",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["true", "false"]
          },
          {
            "name": "dispatchFailed",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["true", "false"]
          },
          {
            "name": "withWork",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["true", "false"]
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/InvoicePageResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "402": {
            "description": "The organisation’s plan does not include the module this operation belongs to (`feature_not_in_plan`, with the missing `feature` named in the body). Not a permission problem — an administrator activates the module in Kelomo.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/crm/leads": {
      "get": {
        "operationId": "LeadsController_list",
        "summary": "Find leads",
        "description": "**Capability:** `crm.use` — an API key must carry this in its scope.",
        "tags": ["CRM"],
        "parameters": [
          {
            "name": "cursor",
            "required": false,
            "in": "query",
            "type": "string"
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "minimum": 1,
            "maximum": 100,
            "default": 50,
            "type": "integer"
          },
          {
            "name": "updatedSince",
            "required": false,
            "in": "query",
            "x-minimum-year": 1900,
            "x-maximum-year": 2999,
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
            "type": "string"
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["new", "working", "converted", "discarded"]
          },
          {
            "name": "ownerMembershipId",
            "required": false,
            "in": "query",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "type": "string"
          },
          {
            "name": "source",
            "required": false,
            "in": "query",
            "type": "string",
            "enum": ["manual", "intake", "import", "website", "referral", "other"]
          },
          {
            "name": "q",
            "required": false,
            "in": "query",
            "maxLength": 200,
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/LeadPageResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "402": {
            "description": "The organisation’s plan does not include the module this operation belongs to (`feature_not_in_plan`, with the missing `feature` named in the body). Not a permission problem — an administrator activates the module in Kelomo.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/webhook-events": {
      "get": {
        "operationId": "WebhookEventsController_list",
        "summary": "List the event catalogue",
        "description": "Every event Kelomo can deliver, with the capability a key must hold to subscribe and the JSON Schema (draft 2020-12) of the delivery’s `data` object. Deliveries carry identifiers, states and timestamps only — read the details you need through the API with the ids in the payload.\n\n**Capability:** none — the operation acts as the authenticated member and its authorisation is row-level (ownership, the approver line, project membership). An API key with an empty scope reaches it.",
        "tags": ["Webhooks"],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/WebhookEventCatalogResponseDto"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    },
    "/v1/webhooks": {
      "post": {
        "operationId": "WebhooksController_create",
        "summary": "When a Kelomo event fires",
        "description": "Subscribes this flow to one Kelomo event. The delivery carries identifiers, state and timestamps only; Event id is the join key to GET /v1/integration-events.",
        "tags": ["Webhooks"],
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": ["url", "events"],
              "properties": {
                "url": {
                  "type": "string",
                  "description": "The Power Automate callback URL. Filled in by the flow.",
                  "x-ms-notification-url": true,
                  "x-ms-visibility": "internal"
                },
                "events": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "enum": [
                      "quote.decided",
                      "batch.created",
                      "status_update.posted",
                      "deal.won",
                      "lead.created",
                      "workday.approved",
                      "export_batch.created",
                      "export_batch.delivered",
                      "export_batch.delivery_failed",
                      "absence.submitted",
                      "absence.decided",
                      "absence.changed",
                      "shift.published",
                      "invoice.issued",
                      "invoice.credited",
                      "invoice.dispatched",
                      "invoice.delivered",
                      "invoice.delivery_failed",
                      "payment.recorded",
                      "payment.allocated",
                      "payment.imported",
                      "invoice.overdue",
                      "invoice.drafts_ready",
                      "recurring_plan.run_completed",
                      "batch.delivered",
                      "workday.confirmed",
                      "workday.returned",
                      "workday.corrected",
                      "workday.unapproved",
                      "absence.cancelled",
                      "absence.certificate_provided",
                      "shift.changed",
                      "open_shift.claimed",
                      "open_shift.claim_decided",
                      "shift_swap.proposed",
                      "shift_swap.decided",
                      "export_batch.cancelled",
                      "payroll_period.locked",
                      "payroll_period.lock_lifted",
                      "member.created",
                      "member.invited",
                      "member.deactivated",
                      "member.deleted",
                      "employment.terminated",
                      "task.created",
                      "task.updated",
                      "task.status_changed",
                      "task.approval_responded",
                      "quote.sent",
                      "quote.accepted",
                      "deal.lost",
                      "deal.stage_changed",
                      "lead.converted",
                      "intake.received",
                      "change_order.approved",
                      "project.created",
                      "project.closed",
                      "contact.anonymized",
                      "lead.anonymized",
                      "invoice.paid",
                      "customer.created",
                      "customer.updated",
                      "project.updated",
                      "member.updated",
                      "shift.unpublished",
                      "activity.created",
                      "activity.completed",
                      "service_order.created",
                      "service_order.completed",
                      "service_visit.completed",
                      "service_report.signed",
                      "service_sla.breached",
                      "service_visit.scheduled",
                      "service_visit.rescheduled",
                      "service_visit.reassigned",
                      "trip.submitted",
                      "trip.approved",
                      "trip.returned",
                      "expense.submitted",
                      "expense.approved",
                      "expense.returned"
                    ]
                  },
                  "description": "The events this flow listens to. One flow, one event keeps the run history readable; the catalogue is at GET /v1/webhook-events.",
                  "x-ms-summary": "Events"
                },
                "format": {
                  "type": "string",
                  "enum": ["json"],
                  "default": "json",
                  "x-ms-visibility": "internal"
                },
                "locale": {
                  "type": "string",
                  "enum": ["fi", "sv", "en"],
                  "default": "fi",
                  "x-ms-summary": "Language of the delivered message"
                }
              }
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created.",
            "schema": {
              "$ref": "#/definitions/CreateWebhookEndpointResponseDto"
            },
            "headers": {
              "Location": {
                "description": "Absolute URL of the subscription just created — `DELETE` it to cancel. Power Automate and Logic Apps unsubscribe through this header when a flow is removed.",
                "type": "string",
                "format": "uri"
              }
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "409": {
            "description": "Conflict with the object’s current state — an append-only boundary, a concurrent edit, or an idempotency-key collision (`idempotency_key_reused`, `idempotency_key_in_flight`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        },
        "x-ms-trigger": "single",
        "x-ms-notification-content": {
          "description": "One Kelomo event delivery.",
          "schema": {
            "type": "object",
            "required": ["id", "event", "integrationEventId", "createdAt", "data"],
            "properties": {
              "id": {
                "type": "string",
                "x-ms-summary": "Delivery id",
                "description": "This delivery; automatic retries and a manual redelivery keep it."
              },
              "event": {
                "type": "string",
                "x-ms-summary": "Event"
              },
              "integrationEventId": {
                "type": "string",
                "x-nullable": true,
                "x-ms-summary": "Event id",
                "description": "The event: deduplicate on it. The join key to GET /v1/integration-events."
              },
              "createdAt": {
                "type": "string",
                "format": "date-time",
                "x-ms-summary": "Created at",
                "description": "When the event happened, the same on every redelivery."
              },
              "data": {
                "type": "object",
                "x-ms-summary": "Payload"
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}": {
      "delete": {
        "operationId": "WebhooksController_remove",
        "summary": "Delete an event subscription",
        "description": "**Capability:** `integrations.manage` — an API key must carry this in its scope.",
        "tags": ["Webhooks"],
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "schema": {
              "$ref": "#/definitions/WebhookOkResponseDto"
            }
          },
          "400": {
            "description": "Malformed request: schema validation failed, a parameter value is out of range, or a required header is missing (`idempotency_key_required`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "401": {
            "description": "Missing, expired or revoked credentials — `invalid_token`, `api_key_expired`, `api_key_owner_inactive`.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "403": {
            "description": "Authenticated but not permitted: the key’s scope or the member’s capabilities do not cover the operation (`insufficient_role`), the key is read-only (`read_only_key`), or the organisation has switched off the module this operation belongs to (`module_not_active`). The last one is not a scope problem and widening the key will not help: the module is licensed but turned off, and an administrator switches it back on in Kelomo → Settings → Features.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "404": {
            "description": "No such object in this organisation — the one the path names, or one the request body refers to by id (`…_not_found`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "409": {
            "description": "Conflict with the object’s current state — an append-only boundary, a concurrent edit, or an idempotency-key collision (`idempotency_key_reused`, `idempotency_key_in_flight`).",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "422": {
            "description": "Well-formed, but refused by a rule of the organisation or of the object’s current state — a locked period, an overlapping absence (`absence_period_overlap`), a day no worktime model covers (`missing_model_version_for_day`). `code` names the rule; repeating the same request does not help.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "429": {
            "description": "Rate limit exceeded for the key or for the organisation. Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "500": {
            "description": "Unexpected server error. Retry idempotent operations; quote `requestId` if it persists.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          },
          "503": {
            "description": "Temporarily unable to serve: the database is congested (`service_busy`) or cannot be reached (`service_unavailable`). Wait `Retry-After` seconds and retry.",
            "schema": {
              "$ref": "#/definitions/Problem"
            }
          }
        }
      }
    }
  },
  "definitions": {
    "AbsencePeriodDetailDto": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string"
        },
        "membershipId": {
          "type": "string"
        },
        "memberName": {
          "type": "string",
          "x-nullable": true
        },
        "policyId": {
          "type": "string"
        },
        "policyCode": {
          "type": "string"
        },
        "policyNameFi": {
          "type": "string"
        },
        "policyNameSv": {
          "type": "string"
        },
        "policyNameEn": {
          "type": "string"
        },
        "sensitivity": {
          "type": "string",
          "enum": ["normal", "restricted", "health"]
        },
        "status": {
          "type": "string",
          "enum": ["draft", "submitted", "approved", "rejected", "cancelled", "withdrawn"]
        },
        "origin": {
          "type": "string",
          "enum": ["employee", "planner"]
        },
        "workflowMode": {
          "type": "string",
          "enum": ["request", "notification", "employer_assignment"]
        },
        "startDate": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "endDate": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "startPartial": {
          "type": "object",
          "properties": {
            "kind": {
              "type": "string",
              "enum": ["morning", "afternoon", "window", "duration"]
            },
            "fromMinutes": {
              "type": "integer",
              "minimum": 0,
              "maximum": 2879
            },
            "toMinutes": {
              "type": "integer",
              "minimum": 1,
              "maximum": 2880
            },
            "minutes": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1440
            }
          },
          "required": ["kind"],
          "default": null,
          "x-nullable": true
        },
        "endPartial": {
          "type": "object",
          "properties": {
            "kind": {
              "type": "string",
              "enum": ["morning", "afternoon", "window", "duration"]
            },
            "fromMinutes": {
              "type": "integer",
              "minimum": 0,
              "maximum": 2879
            },
            "toMinutes": {
              "type": "integer",
              "minimum": 1,
              "maximum": 2880
            },
            "minutes": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1440
            }
          },
          "required": ["kind"],
          "default": null,
          "x-nullable": true
        },
        "consumesQuarterDays": {
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "counterSaturdayQuarterDays": {
          "default": 0,
          "type": "integer",
          "minimum": -9007199254740991,
          "maximum": 9007199254740991
        },
        "consumesMinutes": {
          "default": 0,
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "certificateRequired": {
          "default": false,
          "type": "boolean"
        },
        "certificateStatus": {
          "type": "string",
          "enum": ["missing", "provided", "waived", null],
          "default": null,
          "x-nullable": true
        },
        "reason": {
          "type": "string",
          "x-nullable": true
        },
        "decidedByMembershipId": {
          "type": "string",
          "x-nullable": true
        },
        "decidedByName": {
          "type": "string",
          "x-nullable": true
        },
        "decidedAt": {
          "type": "string",
          "x-nullable": true
        },
        "decisionReason": {
          "type": "string",
          "x-nullable": true
        },
        "proposal": {
          "type": "object",
          "properties": {
            "startDate": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "endDate": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "proposedByMembershipId": {
              "type": "string"
            },
            "proposedByName": {
              "type": "string",
              "x-nullable": true
            },
            "proposedAt": {
              "type": "string"
            },
            "note": {
              "type": "string",
              "x-nullable": true
            }
          },
          "required": [
            "startDate",
            "endDate",
            "proposedByMembershipId",
            "proposedByName",
            "proposedAt",
            "note"
          ],
          "default": null,
          "x-nullable": true
        },
        "noticeDeadline": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
          "default": null,
          "x-nullable": true
        },
        "createdAt": {
          "type": "string"
        },
        "updatedAt": {
          "type": "string"
        },
        "viewerPermissions": {
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "viewCapacity",
              "viewAbsenceDetail",
              "requestForSelf",
              "decideLeave",
              "administerEntitlement"
            ]
          }
        },
        "days": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "workDate": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "workRecordForm": {
                "type": "string",
                "enum": ["none", "day_type", "entry_type"]
              },
              "expectedTargetMinutes": {
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647
              },
              "expectedSource": {
                "type": "string",
                "x-nullable": true
              },
              "absentMinutes": {
                "default": 0,
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647
              },
              "absentStartMinutes": {
                "type": "integer",
                "minimum": 0,
                "maximum": 2879,
                "default": null,
                "x-nullable": true
              },
              "absentEndMinutes": {
                "type": "integer",
                "minimum": 1,
                "maximum": 2880,
                "default": null,
                "x-nullable": true
              },
              "consumesEntitlement": {
                "type": "boolean"
              },
              "consumesQuarterDays": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "consumesAccountMinutes": {
                "default": 0,
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647
              },
              "exclusionReason": {
                "type": "string",
                "enum": [
                  "consumes",
                  "sunday",
                  "rest-day",
                  "exception-day",
                  "public-holiday",
                  "eve",
                  "easter-saturday",
                  "not-leave-policy",
                  "not-workday"
                ]
              },
              "counterSaturdayQuarterDays": {
                "default": 0,
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "sickWaitingDay": {
                "default": false,
                "type": "boolean"
              },
              "sickPayInfo": {
                "type": "object",
                "properties": {
                  "paid": {
                    "type": "boolean"
                  },
                  "percent": {
                    "type": "number"
                  },
                  "paidDayIndex": {
                    "type": "integer",
                    "minimum": -9007199254740991,
                    "maximum": 9007199254740991,
                    "x-nullable": true
                  }
                },
                "required": ["paid", "percent", "paidDayIndex"],
                "default": null,
                "x-nullable": true
              },
              "holidayName": {
                "type": "object",
                "properties": {
                  "fi": {
                    "type": "string"
                  },
                  "sv": {
                    "type": "string"
                  },
                  "en": {
                    "type": "string"
                  }
                },
                "required": ["fi", "sv", "en"],
                "default": null,
                "x-nullable": true
              }
            },
            "required": [
              "workDate",
              "workRecordForm",
              "expectedTargetMinutes",
              "expectedSource",
              "consumesEntitlement",
              "consumesQuarterDays",
              "exclusionReason"
            ]
          }
        },
        "events": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "type": {
                "type": "string",
                "enum": [
                  "submit",
                  "approve",
                  "reject",
                  "withdraw",
                  "cancel",
                  "postpone",
                  "modify",
                  "restore",
                  "certificate_provided",
                  "certificate_waived",
                  "certificate_reset",
                  "propose",
                  "proposal_accepted",
                  "proposal_declined",
                  "proposal_withdrawn"
                ]
              },
              "actorMembershipId": {
                "type": "string"
              },
              "actorName": {
                "type": "string"
              },
              "occurredAt": {
                "type": "string"
              },
              "reason": {
                "type": "string",
                "x-nullable": true
              },
              "removedRanges": {
                "default": [],
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "startDate": {
                      "type": "string",
                      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                    },
                    "endDate": {
                      "type": "string",
                      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                    }
                  },
                  "required": ["startDate", "endDate"]
                }
              }
            },
            "required": ["id", "type", "actorMembershipId", "actorName", "occurredAt", "reason"]
          }
        },
        "comments": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "authorMembershipId": {
                "type": "string"
              },
              "authorName": {
                "type": "string"
              },
              "body": {
                "type": "string"
              },
              "createdAt": {
                "type": "string"
              }
            },
            "required": ["id", "authorMembershipId", "authorName", "body", "createdAt"]
          }
        },
        "conflicts": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "workDate": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "type": {
                "type": "string",
                "enum": [
                  "existing-draft",
                  "confirmed-day",
                  "exported-day",
                  "published-shift",
                  "overlapping-period",
                  "outside-employment",
                  "locked-period"
                ]
              }
            },
            "required": ["workDate", "type"]
          }
        },
        "planningUnitId": {
          "type": "string",
          "x-nullable": true
        },
        "consumesLeaveLedgerKind": {
          "type": "string",
          "enum": [
            "annual_leave",
            "additional_leave",
            "free_time",
            "saved_leave",
            "bonus_leave",
            null
          ],
          "x-nullable": true
        },
        "decisionBasis": {
          "type": "string",
          "default": null,
          "x-nullable": true
        },
        "decisionDays": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "workDate": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "workRecordForm": {
                "type": "string",
                "enum": ["none", "day_type", "entry_type"]
              },
              "expectedTargetMinutes": {
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647
              },
              "expectedSource": {
                "type": "string",
                "x-nullable": true
              },
              "absentMinutes": {
                "default": 0,
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647
              },
              "absentStartMinutes": {
                "type": "integer",
                "minimum": 0,
                "maximum": 2879,
                "default": null,
                "x-nullable": true
              },
              "absentEndMinutes": {
                "type": "integer",
                "minimum": 1,
                "maximum": 2880,
                "default": null,
                "x-nullable": true
              },
              "consumesEntitlement": {
                "type": "boolean"
              },
              "consumesQuarterDays": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "consumesAccountMinutes": {
                "default": 0,
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647
              },
              "exclusionReason": {
                "type": "string",
                "enum": [
                  "consumes",
                  "sunday",
                  "rest-day",
                  "exception-day",
                  "public-holiday",
                  "eve",
                  "easter-saturday",
                  "not-leave-policy",
                  "not-workday"
                ]
              },
              "counterSaturdayQuarterDays": {
                "default": 0,
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "sickWaitingDay": {
                "default": false,
                "type": "boolean"
              },
              "sickPayInfo": {
                "type": "object",
                "properties": {
                  "paid": {
                    "type": "boolean"
                  },
                  "percent": {
                    "type": "number"
                  },
                  "paidDayIndex": {
                    "type": "integer",
                    "minimum": -9007199254740991,
                    "maximum": 9007199254740991,
                    "x-nullable": true
                  }
                },
                "required": ["paid", "percent", "paidDayIndex"],
                "default": null,
                "x-nullable": true
              },
              "holidayName": {
                "type": "object",
                "properties": {
                  "fi": {
                    "type": "string"
                  },
                  "sv": {
                    "type": "string"
                  },
                  "en": {
                    "type": "string"
                  }
                },
                "required": ["fi", "sv", "en"],
                "default": null,
                "x-nullable": true
              }
            },
            "required": [
              "workDate",
              "workRecordForm",
              "expectedTargetMinutes",
              "expectedSource",
              "consumesEntitlement",
              "consumesQuarterDays",
              "exclusionReason"
            ]
          },
          "default": null,
          "x-nullable": true
        },
        "staffingImpact": {
          "type": "object",
          "properties": {
            "status": {
              "type": "string",
              "enum": ["assessed", "not_applicable", "unavailable"]
            },
            "planningUnitId": {
              "type": "string",
              "default": null,
              "x-nullable": true
            },
            "days": {
              "default": [],
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "workDate": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "shortfall": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 9007199254740991
                  },
                  "planningUnitId": {
                    "type": "string",
                    "default": null,
                    "x-nullable": true
                  },
                  "planningUnitName": {
                    "type": "string",
                    "default": null,
                    "x-nullable": true
                  }
                },
                "required": ["workDate", "shortfall"]
              }
            }
          },
          "required": ["status"],
          "default": null,
          "x-nullable": true
        },
        "interruptSelfLiabilityQuarterDays": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991,
          "default": null,
          "x-nullable": true
        },
        "interruptSelfLiabilityYears": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "creditYear": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "from": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "to": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "quarterDays": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              }
            },
            "required": ["creditYear", "from", "to", "quarterDays"]
          },
          "default": null,
          "x-nullable": true
        },
        "counterLeaveYears": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "creditYear": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "from": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "to": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "weekdayQuarterDays": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "counterQuarterDays": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "days": {
                "default": [],
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "workDate": {
                      "type": "string",
                      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                    },
                    "quarterDays": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    }
                  },
                  "required": ["workDate", "quarterDays"]
                }
              },
              "unchargedSaturdays": {
                "default": [],
                "type": "array",
                "items": {
                  "type": "string",
                  "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                }
              }
            },
            "required": ["creditYear", "from", "to", "weekdayQuarterDays", "counterQuarterDays"]
          },
          "default": null,
          "x-nullable": true
        }
      },
      "required": [
        "id",
        "membershipId",
        "memberName",
        "policyId",
        "policyCode",
        "policyNameFi",
        "policyNameSv",
        "policyNameEn",
        "sensitivity",
        "status",
        "origin",
        "workflowMode",
        "startDate",
        "endDate",
        "consumesQuarterDays",
        "reason",
        "decidedByMembershipId",
        "decidedByName",
        "decidedAt",
        "decisionReason",
        "createdAt",
        "updatedAt",
        "viewerPermissions",
        "days",
        "events",
        "comments",
        "conflicts",
        "planningUnitId",
        "consumesLeaveLedgerKind"
      ]
    },
    "AccessContextDto": {
      "type": "object",
      "properties": {
        "tenantId": {
          "type": "string"
        },
        "membershipId": {
          "type": "string"
        },
        "role": {
          "type": "string"
        },
        "capabilities": {
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "worktime.supervise",
              "scheduling.plan",
              "scheduling.admin",
              "projects.manage",
              "projects.read",
              "crm.use",
              "crm.admin",
              "crm.privacy",
              "hr.manage",
              "hr.record",
              "whistleblowing.handle",
              "payroll.run",
              "invoicing.manage",
              "reports.team",
              "members.manage",
              "comms.announce",
              "integrations.manage",
              "service.dispatch",
              "service.admin",
              "org.admin"
            ]
          }
        },
        "modules": {
          "type": "object",
          "properties": {
            "tenant": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "shift_planning",
                  "travel_expense",
                  "hr_management",
                  "dimensions",
                  "project_tracking",
                  "invoicing",
                  "balance_banks",
                  "sso",
                  "calendar_sync",
                  "crm",
                  "field_service"
                ]
              }
            },
            "member": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "shift_planning",
                  "travel_expense",
                  "hr_management",
                  "dimensions",
                  "project_tracking",
                  "invoicing",
                  "balance_banks",
                  "sso",
                  "calendar_sync",
                  "crm",
                  "field_service"
                ]
              }
            }
          },
          "required": ["tenant", "member"]
        },
        "apiKey": {
          "type": "object",
          "properties": {
            "id": {
              "type": "string"
            },
            "kind": {
              "type": "string",
              "enum": ["personal", "service"]
            },
            "scope": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "worktime.supervise",
                  "scheduling.plan",
                  "scheduling.admin",
                  "projects.manage",
                  "projects.read",
                  "crm.use",
                  "crm.admin",
                  "crm.privacy",
                  "hr.manage",
                  "hr.record",
                  "whistleblowing.handle",
                  "payroll.run",
                  "invoicing.manage",
                  "reports.team",
                  "members.manage",
                  "comms.announce",
                  "integrations.manage",
                  "service.dispatch",
                  "service.admin",
                  "org.admin"
                ]
              }
            },
            "readOnly": {
              "type": "boolean"
            },
            "expiresAt": {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
              "x-minimum-year": 1900,
              "x-maximum-year": 2999,
              "x-nullable": true
            }
          },
          "required": ["id", "kind", "scope", "readOnly", "expiresAt"],
          "x-nullable": true
        },
        "oauth": {
          "type": "object",
          "properties": {
            "grantId": {
              "type": "string"
            },
            "clientName": {
              "type": "string"
            },
            "scope": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "worktime.supervise",
                  "scheduling.plan",
                  "scheduling.admin",
                  "projects.manage",
                  "projects.read",
                  "crm.use",
                  "crm.admin",
                  "crm.privacy",
                  "hr.manage",
                  "hr.record",
                  "whistleblowing.handle",
                  "payroll.run",
                  "invoicing.manage",
                  "reports.team",
                  "members.manage",
                  "comms.announce",
                  "integrations.manage",
                  "service.dispatch",
                  "service.admin",
                  "org.admin"
                ]
              }
            },
            "resource": {
              "type": "string",
              "x-nullable": true
            }
          },
          "required": ["grantId", "clientName", "scope", "resource"],
          "x-nullable": true
        },
        "rateLimits": {
          "type": "object",
          "properties": {
            "perKeyPerMinute": {
              "type": "integer",
              "minimum": -9007199254740991,
              "maximum": 9007199254740991
            },
            "perTenantPerMinute": {
              "type": "integer",
              "minimum": -9007199254740991,
              "maximum": 9007199254740991
            }
          },
          "required": ["perKeyPerMinute", "perTenantPerMinute"]
        }
      },
      "required": [
        "tenantId",
        "membershipId",
        "role",
        "capabilities",
        "modules",
        "apiKey",
        "oauth",
        "rateLimits"
      ]
    },
    "ApproveAbsencePeriodDto": {
      "type": "object",
      "properties": {
        "reason": {
          "type": "string",
          "maxLength": 500
        },
        "expectedUpdatedAt": {
          "type": "string",
          "minLength": 1,
          "maxLength": 64,
          "description": "Optimistic lock: the `updatedAt` the editor was looking at, echoed back. A mismatch is 409 with the operation’s stale code. An empty string is not a version and is rejected."
        },
        "reopenRecordedDays": {
          "type": "boolean"
        },
        "partialWindowResolution": {
          "type": "string",
          "enum": ["without_credit", "move_window"]
        },
        "expectedDecisionBasis": {
          "type": "string",
          "minLength": 1,
          "maxLength": 256
        }
      }
    },
    "ApproveWorkDayDto": {
      "type": "object",
      "properties": {
        "overtimeDisposition": {
          "type": "string",
          "enum": ["flex", "payout", "ignore", "bank"]
        },
        "bankAccountTypeId": {
          "type": "string",
          "format": "uuid",
          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
          "x-nullable": true
        },
        "expectedUpdatedAt": {
          "type": "string",
          "minLength": 1,
          "maxLength": 64,
          "description": "Optimistic lock: the `updatedAt` the editor was looking at, echoed back. A mismatch is 409 with the operation’s stale code. An empty string is not a version and is rejected."
        }
      },
      "default": {}
    },
    "BulkCreateCustomersDto": {
      "type": "object",
      "properties": {
        "rows": {
          "minItems": 1,
          "maxItems": 500,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "pattern": "^[a-z0-9][a-z0-9_-]{0,49}$"
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200
              },
              "businessId": {
                "type": "string",
                "maxLength": 20
              },
              "einvoiceAddress": {
                "type": "string",
                "maxLength": 50
              },
              "einvoiceOperator": {
                "type": "string",
                "maxLength": 50
              },
              "invoiceReference": {
                "type": "string",
                "maxLength": 50
              },
              "invoiceEmail": {
                "type": "string",
                "maxLength": 320,
                "format": "email",
                "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
              },
              "streetAddress": {
                "type": "string",
                "maxLength": 200
              },
              "postalCode": {
                "type": "string",
                "maxLength": 20
              },
              "city": {
                "type": "string",
                "maxLength": 100
              },
              "country": {
                "type": "string",
                "maxLength": 2
              },
              "invoiceLanguage": {
                "type": "string",
                "enum": ["fi", "sv", "en"]
              },
              "defaultPaymentTermDays": {
                "type": "integer",
                "minimum": 1,
                "maximum": 90
              },
              "vatId": {
                "type": "string",
                "maxLength": 20
              },
              "website": {
                "type": "string",
                "maxLength": 200
              },
              "industryCode": {
                "type": "string",
                "maxLength": 20
              },
              "notes": {
                "type": "string",
                "maxLength": 8000
              },
              "externalSource": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9_]{1,63}$"
              },
              "externalId": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200
              },
              "active": {
                "type": "boolean"
              }
            }
          }
        },
        "upsert": {
          "default": false,
          "type": "boolean"
        },
        "linkByCode": {
          "default": false,
          "type": "boolean"
        },
        "dryRun": {
          "default": false,
          "type": "boolean"
        },
        "filename": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        },
        "refusedRowCount": {
          "type": "integer",
          "minimum": 0,
          "maximum": 100000
        }
      },
      "required": ["rows"]
    },
    "BulkCreateProjectsDto": {
      "type": "object",
      "properties": {
        "rows": {
          "minItems": 1,
          "maxItems": 500,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "pattern": "^[a-z0-9][a-z0-9_-]{0,49}$"
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200
              },
              "customerCode": {
                "type": "string",
                "pattern": "^[a-z0-9][a-z0-9_-]{0,49}$"
              },
              "customerName": {
                "type": "string",
                "maxLength": 200
              },
              "ownerEmail": {
                "type": "string",
                "maxLength": 254,
                "format": "email",
                "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
              },
              "startDate": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "endDate": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "budgetMinutes": {
                "type": "integer",
                "minimum": 0,
                "maximum": 2147483647
              },
              "budgetAmountCents": {
                "type": "integer",
                "minimum": 0,
                "maximum": 2147483647
              },
              "billingModel": {
                "type": "string",
                "enum": ["time_materials", "fixed_price", "non_billable", "retainer"]
              },
              "status": {
                "type": "string",
                "enum": ["draft", "active", "on_hold", "closed"]
              },
              "externalSource": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9_]{1,63}$"
              },
              "externalId": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200
              }
            }
          }
        },
        "createMissingCustomers": {
          "default": false,
          "type": "boolean"
        },
        "skipResembling": {
          "type": "boolean"
        },
        "upsert": {
          "default": false,
          "type": "boolean"
        },
        "linkByCode": {
          "default": false,
          "type": "boolean"
        },
        "dryRun": {
          "default": false,
          "type": "boolean"
        },
        "filename": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        },
        "refusedRowCount": {
          "type": "integer",
          "minimum": 0,
          "maximum": 100000
        }
      },
      "required": ["rows"]
    },
    "BulkCreateTasksDto": {
      "type": "object",
      "properties": {
        "rows": {
          "minItems": 1,
          "maxItems": 500,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "projectCode": {
                "type": "string",
                "pattern": "^[a-z0-9][a-z0-9_-]{0,49}$"
              },
              "projectExternalSource": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9_]{1,63}$"
              },
              "projectExternalId": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200
              },
              "title": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200
              },
              "description": {
                "type": "string",
                "maxLength": 8000
              },
              "assigneeEmail": {
                "type": "string",
                "maxLength": 254,
                "format": "email",
                "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
              },
              "startDate": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "dueDate": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "estimateMinutes": {
                "type": "integer",
                "minimum": 0,
                "maximum": 2147483647
              },
              "priority": {
                "type": "string",
                "enum": ["low", "normal", "high", "urgent"]
              },
              "done": {
                "type": "boolean"
              },
              "externalSource": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9_]{1,63}$"
              },
              "externalId": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200
              },
              "externalStatus": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100
              }
            }
          }
        },
        "linkByTitle": {
          "default": false,
          "type": "boolean"
        },
        "dryRun": {
          "default": false,
          "type": "boolean"
        },
        "skipResembling": {
          "type": "boolean"
        },
        "filename": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        },
        "refusedRowCount": {
          "type": "integer",
          "minimum": 0,
          "maximum": 100000
        }
      },
      "required": ["rows"]
    },
    "BulkImportResponseDto": {
      "type": "object",
      "properties": {
        "results": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "index": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "status": {
                "type": "string",
                "enum": ["created", "updated", "unchanged", "skipped", "error"]
              },
              "id": {
                "type": "string",
                "x-nullable": true
              },
              "code": {
                "type": "string",
                "x-nullable": true
              },
              "field": {
                "type": "string"
              },
              "targetCode": {
                "type": "string"
              },
              "warnings": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "field": {
                      "type": "string",
                      "description": "The request field the remark is about (`businessId`)."
                    },
                    "code": {
                      "type": "string",
                      "description": "Machine-readable remark the UI translates (`business_id_check_digit`: the value does not pass the Finnish Y-tunnus check digit)."
                    }
                  },
                  "required": ["field", "code"]
                }
              }
            },
            "required": ["index", "status", "id", "code"]
          }
        },
        "dryRun": {
          "type": "boolean"
        }
      },
      "required": ["results"]
    },
    "BulkUpsertTrackingTargetsDto": {
      "type": "object",
      "properties": {
        "level": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9_]{1,49}$"
        },
        "linkByCode": {
          "default": false,
          "type": "boolean"
        },
        "dryRun": {
          "default": false,
          "type": "boolean"
        },
        "rows": {
          "minItems": 1,
          "maxItems": 500,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "minLength": 1,
                "maxLength": 50
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200
              },
              "externalSource": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9_]{1,63}$"
              },
              "externalId": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200
              },
              "parentCode": {
                "type": "string",
                "minLength": 1,
                "maxLength": 50,
                "x-nullable": true
              },
              "active": {
                "type": "boolean"
              }
            },
            "required": ["code"]
          }
        },
        "filename": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        },
        "refusedRowCount": {
          "type": "integer",
          "minimum": 0,
          "maximum": 100000
        }
      },
      "required": ["level", "rows"]
    },
    "BulkUpsertTrackingTargetsResponseDto": {
      "type": "object",
      "properties": {
        "results": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "index": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "status": {
                "type": "string",
                "enum": ["created", "updated", "unchanged", "skipped", "error"]
              },
              "id": {
                "type": "string",
                "x-nullable": true
              },
              "code": {
                "type": "string",
                "x-nullable": true
              },
              "field": {
                "type": "string"
              },
              "targetCode": {
                "type": "string"
              },
              "warnings": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "field": {
                      "type": "string",
                      "description": "The request field the remark is about (`businessId`)."
                    },
                    "code": {
                      "type": "string",
                      "description": "Machine-readable remark the UI translates (`business_id_check_digit`: the value does not pass the Finnish Y-tunnus check digit)."
                    }
                  },
                  "required": ["field", "code"]
                }
              }
            },
            "required": ["index", "status", "id", "code"]
          }
        },
        "dryRun": {
          "type": "boolean"
        }
      },
      "required": ["results", "dryRun"]
    },
    "CreateMembershipDto": {
      "type": "object",
      "properties": {
        "email": {
          "type": "string",
          "maxLength": 320,
          "format": "email",
          "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
        },
        "firstNames": {
          "type": "string",
          "minLength": 1,
          "maxLength": 100
        },
        "lastName": {
          "type": "string",
          "minLength": 1,
          "maxLength": 100
        },
        "preferredName": {
          "type": "string",
          "minLength": 1,
          "maxLength": 60,
          "x-nullable": true
        },
        "role": {
          "type": "string",
          "enum": ["employee", "manager", "admin", "payroll", "hr"]
        },
        "phone": {
          "type": "string",
          "maxLength": 40
        },
        "managerMembershipId": {
          "type": "string",
          "format": "uuid",
          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
          "x-nullable": true
        },
        "worktimeModelId": {
          "type": "string",
          "format": "uuid",
          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
          "x-nullable": true
        },
        "leaveGroupId": {
          "type": "string",
          "format": "uuid",
          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
          "x-nullable": true
        },
        "planningUnitId": {
          "type": "string",
          "format": "uuid",
          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
        },
        "initialPassword": {
          "type": "string",
          "minLength": 10,
          "maxLength": 200
        },
        "locale": {
          "type": "string",
          "enum": ["fi", "sv", "en"]
        },
        "baseLicense": {
          "type": "boolean"
        },
        "moduleAccess": {
          "maxItems": 6,
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "shift_planning",
              "travel_expense",
              "hr",
              "project_management",
              "crm",
              "field_service"
            ]
          }
        },
        "employmentStartDate": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "employmentEndDate": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "jobTitle": {
          "type": "string",
          "maxLength": 120
        },
        "employmentType": {
          "type": "string",
          "enum": ["permanent", "fixed_term"]
        },
        "fixedTermBasis": {
          "type": "string",
          "enum": ["justified_reason", "no_justified_reason", "long_term_unemployed"]
        },
        "fixedTermGrounds": {
          "type": "string",
          "maxLength": 500
        },
        "payType": {
          "type": "string",
          "enum": ["monthly", "hourly"]
        },
        "employerId": {
          "type": "string",
          "format": "uuid",
          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
        },
        "payrollNumber": {
          "type": "string",
          "maxLength": 40
        },
        "inviteOn": {
          "type": "string",
          "enum": ["now", "employment_start"]
        }
      },
      "required": ["firstNames", "lastName", "role"],
      "additionalProperties": false
    },
    "CreateMembershipResponseDto": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string"
        },
        "invitation": {
          "type": "object",
          "properties": {
            "inviteUrl": {
              "type": "string",
              "x-nullable": true
            },
            "emailed": {
              "type": "boolean"
            },
            "expiresAt": {
              "type": "string"
            }
          },
          "required": ["inviteUrl", "emailed", "expiresAt"],
          "x-nullable": true
        },
        "invitationScheduledFor": {
          "type": "string",
          "x-nullable": true
        },
        "moduleGrants": {
          "type": "object",
          "properties": {
            "granted": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "shift_planning",
                  "travel_expense",
                  "hr",
                  "project_management",
                  "crm",
                  "field_service"
                ]
              }
            },
            "seatLimited": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "shift_planning",
                  "travel_expense",
                  "hr",
                  "project_management",
                  "crm",
                  "field_service"
                ]
              }
            }
          },
          "required": ["granted", "seatLimited"]
        },
        "initialPasswordIgnored": {
          "type": "boolean"
        }
      },
      "required": [
        "id",
        "invitation",
        "invitationScheduledFor",
        "moduleGrants",
        "initialPasswordIgnored"
      ]
    },
    "CreateWebhookEndpointResponseDto": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string"
        },
        "secret": {
          "type": "string",
          "x-nullable": true
        }
      },
      "required": ["id", "secret"]
    },
    "CustomerGetResponseDto": {
      "type": "object",
      "properties": {
        "customer": {
          "type": "object",
          "properties": {
            "id": {
              "type": "string",
              "description": "Customer id — the `customerId` other resources reference."
            },
            "code": {
              "type": "string",
              "description": "Tenant-chosen identifier (lowercase letters, digits, `-` and `_`), unique within the tenant. IMMUTABLE: no update operation accepts it."
            },
            "name": {
              "type": "string",
              "description": "Account name, and the key the register pages by."
            },
            "businessId": {
              "type": "string",
              "description": "Company registration number (Finnish Y-tunnus). MASKED to `null` for a caller without the `invoicing.manage` capability, so `null` here means either \"not set\" or \"you may not read it\" — the two are indistinguishable on the wire by design.",
              "x-nullable": true
            },
            "einvoiceAddress": {
              "type": "string",
              "description": "E-invoicing address of the buyer. Masked to `null` without `invoicing.manage`, so `null` can mean masked rather than unset.",
              "x-nullable": true
            },
            "einvoiceOperator": {
              "type": "string",
              "description": "E-invoicing operator (välittäjä) identifier. Masked to `null` without `invoicing.manage`.",
              "x-nullable": true
            },
            "invoiceReference": {
              "type": "string",
              "description": "Reference the billing export puts on this buyer’s invoices. Masked to `null` without `invoicing.manage`.",
              "x-nullable": true
            },
            "billingRoundingMinutes": {
              "type": "integer",
              "minimum": -9007199254740991,
              "maximum": 9007199254740991,
              "description": "Contractual minimum billing increment in minutes; `null` = no rounding. Configuration rather than billing PII, so it is NOT masked — every register reader sees it.",
              "x-nullable": true
            },
            "active": {
              "type": "boolean",
              "description": "False = deactivated. Customers are never deleted, so history stays intact and there is no `customer.deleted` event: this flag IS the tombstone. A deactivated row is omitted from ordinary listing unless `includeInactive` is set, but stays in an `updatedSince` sweep, which is where a mirror learns the account was retired."
            },
            "sortOrder": {
              "type": "integer",
              "minimum": -2147483648,
              "maximum": 2147483647,
              "description": "RESERVED and deliberately unread: no surface orders by it — the register is keyset-paged by `(name, id)`. Published, so it is kept rather than removed."
            },
            "projectCount": {
              "type": "integer",
              "minimum": -9007199254740991,
              "maximum": 9007199254740991,
              "description": "Number of projects under this customer, any status."
            },
            "streetAddress": {
              "type": "string",
              "description": "Street address of the account.",
              "x-nullable": true
            },
            "postalCode": {
              "type": "string",
              "description": "Postal code of the account address.",
              "x-nullable": true
            },
            "city": {
              "type": "string",
              "description": "City of the account address.",
              "x-nullable": true
            },
            "country": {
              "type": "string",
              "description": "Two-letter country code (ISO 3166-1 alpha-2).",
              "x-nullable": true
            },
            "website": {
              "type": "string",
              "description": "The account’s public website.",
              "x-nullable": true
            },
            "industryCode": {
              "type": "string",
              "description": "Statistics Finland / YTJ industry code (TOL).",
              "x-nullable": true
            },
            "notes": {
              "type": "string",
              "description": "Free-text notes about the account.",
              "x-nullable": true
            },
            "parentCustomerId": {
              "type": "string",
              "description": "The account this one belongs to (group parent / head office). One level covers the common shapes — a chain, a property manager, a group — and a cycle is refused at the write edge. Absent = a top-level account.",
              "x-nullable": true
            },
            "ownerMembershipId": {
              "type": "string",
              "description": "Membership id of the account owner (asiakasvastaava); null when unowned.",
              "x-nullable": true
            },
            "ownerName": {
              "type": "string",
              "description": "Display name of the owner membership, resolved server-side in the same read. Null only when the membership could not be joined.",
              "x-nullable": true
            },
            "defaultContactId": {
              "type": "string",
              "description": "Id of this account’s default contact.",
              "x-nullable": true
            },
            "vatId": {
              "type": "string",
              "description": "VAT number of the buyer. Masked to `null` without `invoicing.manage`.",
              "x-nullable": true
            },
            "vatTreatment": {
              "type": "string",
              "enum": [
                "standard",
                "reverse_charge_construction",
                "eu_service",
                "eu_goods",
                "export",
                "tax_exempt"
              ],
              "description": "Default VAT handling for this buyer: `standard`, `reverse_charge_construction` (rakentamispalvelun käännetty verovelvollisuus), `eu_service`, `eu_goods`, `export` or `tax_exempt`. The one invoicing field that is NOT masked — every register reader may read it, though only `invoicing.manage` may write it."
            },
            "vatIdVerification": {
              "type": "string",
              "enum": ["valid", "invalid", "unavailable", null],
              "description": "Result of the last VIES check of `vatId`: `valid`, `invalid` or `unavailable` (the service did not answer). Null when the id has never been checked or has changed since. Masked to `null` without `invoicing.manage`.",
              "x-nullable": true
            },
            "vatIdVerifiedAt": {
              "type": "string",
              "description": "When the last VIES check ran (ISO instant); null = never. Masked like `vatId`.",
              "x-nullable": true
            },
            "taxExemptReason": {
              "type": "string",
              "description": "The grounds of a `tax_exempt` sale this customer’s invoices state by default (AVL 209 e § 13 k.), e.g. \"Veroton terveyden- ja sairaanhoitopalvelu, AVL 34 §\". A new invoice draft copies it into its own `taxExemptReason`, which the draft may change. Masked to `null` without `invoicing.manage`.",
              "x-nullable": true
            },
            "vatTreatmentRequired": {
              "type": "boolean",
              "description": "True when this is a business buyer in another EU member state (VAT number prefix or country, not FI) under the `standard` treatment: sales documents for it are refused with `vat_treatment_required_for_eu_customer` until its VAT treatment is chosen. Derived on the server; not masked."
            },
            "invoiceEmail": {
              "type": "string",
              "description": "Address email invoices are sent to. Masked to `null` without `invoicing.manage`, so `null` can mean masked rather than unset.",
              "x-nullable": true
            },
            "invoiceLanguage": {
              "type": "string",
              "enum": ["fi", "sv", "en", null],
              "description": "Language of the invoice document (`fi`, `sv`, `en`); null = the seller profile’s default. Masked to `null` without `invoicing.manage`.",
              "x-nullable": true
            },
            "deliveryMethod": {
              "type": "string",
              "enum": ["einvoice", "email", "manual", null],
              "description": "Preferred delivery channel (`einvoice`, `email`, `manual`). Nullable ONLY because of masking: the stored column is NOT NULL with default `manual`, so a `null` here means the caller lacks `invoicing.manage` — never that the value is unset. Do not send that `null` back on update; that field is not nullable.",
              "x-nullable": true
            },
            "defaultPaymentTermDays": {
              "type": "integer",
              "minimum": -9007199254740991,
              "maximum": 9007199254740991,
              "description": "Net payment days for this buyer; null = the seller profile’s term. Masked to `null` without `invoicing.manage`.",
              "x-nullable": true
            },
            "buyerReferenceDefault": {
              "type": "string",
              "description": "The buyer’s own reference (tilaajan viite) prefilled on every invoice document. Masked to `null` without `invoicing.manage`.",
              "x-nullable": true
            },
            "invoicingNotes": {
              "type": "string",
              "description": "Internal notes for whoever bills this customer; never rendered on a document. Masked to `null` without `invoicing.manage`.",
              "x-nullable": true
            },
            "fixedReferenceNumber": {
              "type": "string",
              "description": "The buyer’s fixed reference number (vakioviite), quoted on every payment instead of a per-invoice one — so an incoming payment carrying it is allocated to this customer’s OLDEST open invoice. Masked to `null` without `invoicing.manage`; absent when the field is not carried at all.",
              "x-nullable": true
            },
            "paymentTermId": {
              "type": "string",
              "description": "Id of the payment term this buyer is billed on; null = the seller profile’s default. Masked to `null` without `invoicing.manage`.",
              "x-nullable": true
            },
            "paymentTermName": {
              "type": "string",
              "description": "Human-readable label of `paymentTermId` (e.g. \"14 pv netto, 8 pv −2 %\"). Masked to `null` without `invoicing.manage`.",
              "x-nullable": true
            },
            "openDealCount": {
              "type": "integer",
              "minimum": -9007199254740991,
              "maximum": 9007199254740991,
              "description": "Open deals on this account; 0 for a tenant without CRM data."
            },
            "openPipelineValueCents": {
              "type": "integer",
              "minimum": -9007199254740991,
              "maximum": 9007199254740991,
              "description": "Sum of the open deals’ values in cents; 0 for a tenant without CRM data."
            },
            "lastActivityAt": {
              "type": "string",
              "description": "RFC 3339 timestamp of the LAST CONTACT MADE: the latest `doneAt` among this account’s completed CRM activities. Null while nobody has been in touch. A scheduled step is the future, not a contact, so an open reminder never dates this — not even an overdue one.",
              "x-nullable": true
            },
            "tags": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "CRM tag id."
                  },
                  "name": {
                    "type": "string",
                    "description": "Tag label as the tenant wrote it."
                  },
                  "color": {
                    "type": "string",
                    "description": "Tag colour as a `#rrggbb` hex string, or null when the tenant set none.",
                    "x-nullable": true
                  }
                },
                "required": ["id", "name", "color"]
              },
              "description": "CRM tags on this account; empty when it has none."
            },
            "customFields": {
              "type": "object",
              "additionalProperties": {},
              "description": "Tenant-defined extension fields, keyed by definition key; `{}` when the tenant defines none."
            },
            "externalSource": {
              "type": "string",
              "default": null,
              "description": "Source namespace of an imported account (`hubspot_acme_company`), naming the system, its instance and the record type; null for an account created in Kelomo. A continuous sync matches on this pair before the code.",
              "x-nullable": true
            },
            "externalId": {
              "type": "string",
              "default": null,
              "description": "The source system’s own key for this account; null when it was created in Kelomo.",
              "x-nullable": true
            },
            "updatedAt": {
              "type": "string",
              "description": "RFC 3339 timestamp of the last change to the customer RECORD — the watermark an incremental mirror stores and sends back as `updatedSince`. The record only: the CRM enrichment above (open deals, last activity, tags) is aggregated from other tables and moves without touching this stamp."
            }
          },
          "required": [
            "id",
            "code",
            "name",
            "businessId",
            "einvoiceAddress",
            "einvoiceOperator",
            "invoiceReference",
            "billingRoundingMinutes",
            "active",
            "sortOrder",
            "projectCount",
            "streetAddress",
            "postalCode",
            "city",
            "country",
            "website",
            "industryCode",
            "notes",
            "parentCustomerId",
            "ownerMembershipId",
            "ownerName",
            "defaultContactId",
            "vatId",
            "vatTreatment",
            "vatIdVerification",
            "vatIdVerifiedAt",
            "taxExemptReason",
            "vatTreatmentRequired",
            "invoiceEmail",
            "invoiceLanguage",
            "deliveryMethod",
            "defaultPaymentTermDays",
            "buyerReferenceDefault",
            "invoicingNotes",
            "openDealCount",
            "openPipelineValueCents",
            "lastActivityAt",
            "tags",
            "customFields",
            "updatedAt"
          ],
          "description": "The requested customer register row."
        }
      },
      "required": ["customer"]
    },
    "CustomerPageResponseDto": {
      "type": "object",
      "properties": {
        "items": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "Customer id — the `customerId` other resources reference."
              },
              "code": {
                "type": "string",
                "description": "Tenant-chosen identifier (lowercase letters, digits, `-` and `_`), unique within the tenant. IMMUTABLE: no update operation accepts it."
              },
              "name": {
                "type": "string",
                "description": "Account name, and the key the register pages by."
              },
              "businessId": {
                "type": "string",
                "description": "Company registration number (Finnish Y-tunnus). MASKED to `null` for a caller without the `invoicing.manage` capability, so `null` here means either \"not set\" or \"you may not read it\" — the two are indistinguishable on the wire by design.",
                "x-nullable": true
              },
              "einvoiceAddress": {
                "type": "string",
                "description": "E-invoicing address of the buyer. Masked to `null` without `invoicing.manage`, so `null` can mean masked rather than unset.",
                "x-nullable": true
              },
              "einvoiceOperator": {
                "type": "string",
                "description": "E-invoicing operator (välittäjä) identifier. Masked to `null` without `invoicing.manage`.",
                "x-nullable": true
              },
              "invoiceReference": {
                "type": "string",
                "description": "Reference the billing export puts on this buyer’s invoices. Masked to `null` without `invoicing.manage`.",
                "x-nullable": true
              },
              "billingRoundingMinutes": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991,
                "description": "Contractual minimum billing increment in minutes; `null` = no rounding. Configuration rather than billing PII, so it is NOT masked — every register reader sees it.",
                "x-nullable": true
              },
              "active": {
                "type": "boolean",
                "description": "False = deactivated. Customers are never deleted, so history stays intact and there is no `customer.deleted` event: this flag IS the tombstone. A deactivated row is omitted from ordinary listing unless `includeInactive` is set, but stays in an `updatedSince` sweep, which is where a mirror learns the account was retired."
              },
              "sortOrder": {
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647,
                "description": "RESERVED and deliberately unread: no surface orders by it — the register is keyset-paged by `(name, id)`. Published, so it is kept rather than removed."
              },
              "projectCount": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991,
                "description": "Number of projects under this customer, any status."
              },
              "streetAddress": {
                "type": "string",
                "description": "Street address of the account.",
                "x-nullable": true
              },
              "postalCode": {
                "type": "string",
                "description": "Postal code of the account address.",
                "x-nullable": true
              },
              "city": {
                "type": "string",
                "description": "City of the account address.",
                "x-nullable": true
              },
              "country": {
                "type": "string",
                "description": "Two-letter country code (ISO 3166-1 alpha-2).",
                "x-nullable": true
              },
              "website": {
                "type": "string",
                "description": "The account’s public website.",
                "x-nullable": true
              },
              "industryCode": {
                "type": "string",
                "description": "Statistics Finland / YTJ industry code (TOL).",
                "x-nullable": true
              },
              "notes": {
                "type": "string",
                "description": "Free-text notes about the account.",
                "x-nullable": true
              },
              "parentCustomerId": {
                "type": "string",
                "description": "The account this one belongs to (group parent / head office). One level covers the common shapes — a chain, a property manager, a group — and a cycle is refused at the write edge. Absent = a top-level account.",
                "x-nullable": true
              },
              "ownerMembershipId": {
                "type": "string",
                "description": "Membership id of the account owner (asiakasvastaava); null when unowned.",
                "x-nullable": true
              },
              "ownerName": {
                "type": "string",
                "description": "Display name of the owner membership, resolved server-side in the same read. Null only when the membership could not be joined.",
                "x-nullable": true
              },
              "defaultContactId": {
                "type": "string",
                "description": "Id of this account’s default contact.",
                "x-nullable": true
              },
              "vatId": {
                "type": "string",
                "description": "VAT number of the buyer. Masked to `null` without `invoicing.manage`.",
                "x-nullable": true
              },
              "vatTreatment": {
                "type": "string",
                "enum": [
                  "standard",
                  "reverse_charge_construction",
                  "eu_service",
                  "eu_goods",
                  "export",
                  "tax_exempt"
                ],
                "description": "Default VAT handling for this buyer: `standard`, `reverse_charge_construction` (rakentamispalvelun käännetty verovelvollisuus), `eu_service`, `eu_goods`, `export` or `tax_exempt`. The one invoicing field that is NOT masked — every register reader may read it, though only `invoicing.manage` may write it."
              },
              "vatIdVerification": {
                "type": "string",
                "enum": ["valid", "invalid", "unavailable", null],
                "description": "Result of the last VIES check of `vatId`: `valid`, `invalid` or `unavailable` (the service did not answer). Null when the id has never been checked or has changed since. Masked to `null` without `invoicing.manage`.",
                "x-nullable": true
              },
              "vatIdVerifiedAt": {
                "type": "string",
                "description": "When the last VIES check ran (ISO instant); null = never. Masked like `vatId`.",
                "x-nullable": true
              },
              "taxExemptReason": {
                "type": "string",
                "description": "The grounds of a `tax_exempt` sale this customer’s invoices state by default (AVL 209 e § 13 k.), e.g. \"Veroton terveyden- ja sairaanhoitopalvelu, AVL 34 §\". A new invoice draft copies it into its own `taxExemptReason`, which the draft may change. Masked to `null` without `invoicing.manage`.",
                "x-nullable": true
              },
              "vatTreatmentRequired": {
                "type": "boolean",
                "description": "True when this is a business buyer in another EU member state (VAT number prefix or country, not FI) under the `standard` treatment: sales documents for it are refused with `vat_treatment_required_for_eu_customer` until its VAT treatment is chosen. Derived on the server; not masked."
              },
              "invoiceEmail": {
                "type": "string",
                "description": "Address email invoices are sent to. Masked to `null` without `invoicing.manage`, so `null` can mean masked rather than unset.",
                "x-nullable": true
              },
              "invoiceLanguage": {
                "type": "string",
                "enum": ["fi", "sv", "en", null],
                "description": "Language of the invoice document (`fi`, `sv`, `en`); null = the seller profile’s default. Masked to `null` without `invoicing.manage`.",
                "x-nullable": true
              },
              "deliveryMethod": {
                "type": "string",
                "enum": ["einvoice", "email", "manual", null],
                "description": "Preferred delivery channel (`einvoice`, `email`, `manual`). Nullable ONLY because of masking: the stored column is NOT NULL with default `manual`, so a `null` here means the caller lacks `invoicing.manage` — never that the value is unset. Do not send that `null` back on update; that field is not nullable.",
                "x-nullable": true
              },
              "defaultPaymentTermDays": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991,
                "description": "Net payment days for this buyer; null = the seller profile’s term. Masked to `null` without `invoicing.manage`.",
                "x-nullable": true
              },
              "buyerReferenceDefault": {
                "type": "string",
                "description": "The buyer’s own reference (tilaajan viite) prefilled on every invoice document. Masked to `null` without `invoicing.manage`.",
                "x-nullable": true
              },
              "invoicingNotes": {
                "type": "string",
                "description": "Internal notes for whoever bills this customer; never rendered on a document. Masked to `null` without `invoicing.manage`.",
                "x-nullable": true
              },
              "fixedReferenceNumber": {
                "type": "string",
                "description": "The buyer’s fixed reference number (vakioviite), quoted on every payment instead of a per-invoice one — so an incoming payment carrying it is allocated to this customer’s OLDEST open invoice. Masked to `null` without `invoicing.manage`; absent when the field is not carried at all.",
                "x-nullable": true
              },
              "paymentTermId": {
                "type": "string",
                "description": "Id of the payment term this buyer is billed on; null = the seller profile’s default. Masked to `null` without `invoicing.manage`.",
                "x-nullable": true
              },
              "paymentTermName": {
                "type": "string",
                "description": "Human-readable label of `paymentTermId` (e.g. \"14 pv netto, 8 pv −2 %\"). Masked to `null` without `invoicing.manage`.",
                "x-nullable": true
              },
              "openDealCount": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991,
                "description": "Open deals on this account; 0 for a tenant without CRM data."
              },
              "openPipelineValueCents": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991,
                "description": "Sum of the open deals’ values in cents; 0 for a tenant without CRM data."
              },
              "lastActivityAt": {
                "type": "string",
                "description": "RFC 3339 timestamp of the LAST CONTACT MADE: the latest `doneAt` among this account’s completed CRM activities. Null while nobody has been in touch. A scheduled step is the future, not a contact, so an open reminder never dates this — not even an overdue one.",
                "x-nullable": true
              },
              "tags": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "CRM tag id."
                    },
                    "name": {
                      "type": "string",
                      "description": "Tag label as the tenant wrote it."
                    },
                    "color": {
                      "type": "string",
                      "description": "Tag colour as a `#rrggbb` hex string, or null when the tenant set none.",
                      "x-nullable": true
                    }
                  },
                  "required": ["id", "name", "color"]
                },
                "description": "CRM tags on this account; empty when it has none."
              },
              "customFields": {
                "type": "object",
                "additionalProperties": {},
                "description": "Tenant-defined extension fields, keyed by definition key; `{}` when the tenant defines none."
              },
              "externalSource": {
                "type": "string",
                "default": null,
                "description": "Source namespace of an imported account (`hubspot_acme_company`), naming the system, its instance and the record type; null for an account created in Kelomo. A continuous sync matches on this pair before the code.",
                "x-nullable": true
              },
              "externalId": {
                "type": "string",
                "default": null,
                "description": "The source system’s own key for this account; null when it was created in Kelomo.",
                "x-nullable": true
              },
              "updatedAt": {
                "type": "string",
                "description": "RFC 3339 timestamp of the last change to the customer RECORD — the watermark an incremental mirror stores and sends back as `updatedSince`. The record only: the CRM enrichment above (open deals, last activity, tags) is aggregated from other tables and moves without touching this stamp."
              }
            },
            "required": [
              "id",
              "code",
              "name",
              "businessId",
              "einvoiceAddress",
              "einvoiceOperator",
              "invoiceReference",
              "billingRoundingMinutes",
              "active",
              "sortOrder",
              "projectCount",
              "streetAddress",
              "postalCode",
              "city",
              "country",
              "website",
              "industryCode",
              "notes",
              "parentCustomerId",
              "ownerMembershipId",
              "ownerName",
              "defaultContactId",
              "vatId",
              "vatTreatment",
              "vatIdVerification",
              "vatIdVerifiedAt",
              "taxExemptReason",
              "vatTreatmentRequired",
              "invoiceEmail",
              "invoiceLanguage",
              "deliveryMethod",
              "defaultPaymentTermDays",
              "buyerReferenceDefault",
              "invoicingNotes",
              "openDealCount",
              "openPipelineValueCents",
              "lastActivityAt",
              "tags",
              "customFields",
              "updatedAt"
            ]
          }
        },
        "pageInfo": {
          "type": "object",
          "properties": {
            "nextCursor": {
              "type": "string",
              "x-nullable": true
            },
            "hasMore": {
              "type": "boolean"
            }
          },
          "required": ["nextCursor", "hasMore"]
        },
        "totalCount": {
          "description": "How many accounts match the filters in all. Sent on the first page (no cursor) only.",
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        }
      },
      "required": ["items", "pageInfo"]
    },
    "DimensionListResponseDto": {
      "type": "object",
      "properties": {
        "dimensions": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "code": {
                "type": "string"
              },
              "nameFi": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100
              },
              "nameSv": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100
              },
              "nameEn": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100
              },
              "required": {
                "type": "boolean"
              },
              "active": {
                "type": "boolean"
              },
              "locked": {
                "type": "boolean"
              },
              "allowsSplit": {
                "default": false,
                "type": "boolean"
              },
              "askInEntry": {
                "type": "boolean"
              },
              "sortOrder": {
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647
              },
              "values": {
                "default": [],
                "description": "The whole value tree, only with includeValues=true; empty by default. Page GET /v1/dimensions/{id}/values instead.",
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "parentId": {
                      "type": "string",
                      "x-nullable": true
                    },
                    "code": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "active": {
                      "type": "boolean"
                    },
                    "sortOrder": {
                      "type": "integer",
                      "minimum": -2147483648,
                      "maximum": 2147483647
                    },
                    "path": {
                      "default": [],
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "defaultBillable": {
                      "type": "boolean",
                      "default": null,
                      "x-nullable": true
                    },
                    "customerName": {
                      "type": "string",
                      "default": null,
                      "x-nullable": true
                    },
                    "attributionPrecision": {
                      "type": "string",
                      "enum": ["project", "phase", "task", null],
                      "default": null,
                      "x-nullable": true
                    },
                    "externalSource": {
                      "type": "string",
                      "default": null,
                      "x-nullable": true
                    },
                    "externalId": {
                      "type": "string",
                      "default": null,
                      "x-nullable": true
                    },
                    "kind": {
                      "default": "plain",
                      "type": "string",
                      "enum": [
                        "project",
                        "phase",
                        "task",
                        "service_customer",
                        "service_agreement",
                        "service_order",
                        "plain"
                      ]
                    },
                    "updatedAt": {
                      "type": "string"
                    }
                  },
                  "required": ["id", "parentId", "code", "name", "active", "sortOrder", "updatedAt"]
                }
              },
              "valueCount": {
                "default": 0,
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "recentValues": {
                "default": [],
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "parentId": {
                      "type": "string",
                      "x-nullable": true
                    },
                    "code": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "active": {
                      "type": "boolean"
                    },
                    "sortOrder": {
                      "type": "integer",
                      "minimum": -2147483648,
                      "maximum": 2147483647
                    },
                    "path": {
                      "default": [],
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "defaultBillable": {
                      "type": "boolean",
                      "default": null,
                      "x-nullable": true
                    },
                    "customerName": {
                      "type": "string",
                      "default": null,
                      "x-nullable": true
                    },
                    "attributionPrecision": {
                      "type": "string",
                      "enum": ["project", "phase", "task", null],
                      "default": null,
                      "x-nullable": true
                    },
                    "externalSource": {
                      "type": "string",
                      "default": null,
                      "x-nullable": true
                    },
                    "externalId": {
                      "type": "string",
                      "default": null,
                      "x-nullable": true
                    },
                    "kind": {
                      "default": "plain",
                      "type": "string",
                      "enum": [
                        "project",
                        "phase",
                        "task",
                        "service_customer",
                        "service_agreement",
                        "service_order",
                        "plain"
                      ]
                    },
                    "updatedAt": {
                      "type": "string"
                    }
                  },
                  "required": ["id", "parentId", "code", "name", "active", "sortOrder", "updatedAt"]
                }
              }
            },
            "required": [
              "id",
              "code",
              "nameFi",
              "nameSv",
              "nameEn",
              "required",
              "active",
              "locked",
              "askInEntry",
              "sortOrder"
            ]
          }
        }
      },
      "required": ["dimensions"]
    },
    "DimensionValueListResponseDto": {
      "type": "object",
      "properties": {
        "items": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "parentId": {
                "type": "string",
                "x-nullable": true
              },
              "code": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "active": {
                "type": "boolean"
              },
              "sortOrder": {
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647
              },
              "path": {
                "default": [],
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "defaultBillable": {
                "type": "boolean",
                "default": null,
                "x-nullable": true
              },
              "customerName": {
                "type": "string",
                "default": null,
                "x-nullable": true
              },
              "attributionPrecision": {
                "type": "string",
                "enum": ["project", "phase", "task", null],
                "default": null,
                "x-nullable": true
              },
              "externalSource": {
                "type": "string",
                "default": null,
                "x-nullable": true
              },
              "externalId": {
                "type": "string",
                "default": null,
                "x-nullable": true
              },
              "kind": {
                "default": "plain",
                "type": "string",
                "enum": [
                  "project",
                  "phase",
                  "task",
                  "service_customer",
                  "service_agreement",
                  "service_order",
                  "plain"
                ]
              },
              "updatedAt": {
                "type": "string"
              }
            },
            "required": ["id", "parentId", "code", "name", "active", "sortOrder", "updatedAt"]
          }
        },
        "pageInfo": {
          "type": "object",
          "properties": {
            "nextCursor": {
              "type": "string",
              "x-nullable": true
            },
            "hasMore": {
              "type": "boolean"
            }
          },
          "required": ["nextCursor", "hasMore"]
        }
      },
      "required": ["items", "pageInfo"]
    },
    "EntryTypeListResponseDto": {
      "type": "object",
      "properties": {
        "entryTypes": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "code": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9_]{1,49}$"
              },
              "nameFi": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100
              },
              "nameSv": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100
              },
              "nameEn": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100
              },
              "active": {
                "type": "boolean"
              },
              "isSystem": {
                "type": "boolean"
              },
              "locked": {
                "type": "boolean"
              },
              "governedByAbsence": {
                "default": false,
                "type": "boolean"
              },
              "sortOrder": {
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647
              },
              "versions": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "validFrom": {
                      "type": "string",
                      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                    },
                    "validTo": {
                      "type": "string",
                      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                      "x-nullable": true
                    },
                    "countsAsWorkTime": {
                      "type": "boolean"
                    },
                    "accruesFlex": {
                      "type": "boolean"
                    },
                    "creditsTarget": {
                      "default": false,
                      "type": "boolean"
                    },
                    "countsTowardMaxWorkTime": {
                      "type": "boolean"
                    },
                    "emergencyWork": {
                      "default": false,
                      "type": "boolean"
                    },
                    "requiresComment": {
                      "default": false,
                      "type": "boolean"
                    },
                    "paid": {
                      "type": "boolean"
                    },
                    "maxDurationMinutes": {
                      "type": "integer",
                      "minimum": -2147483648,
                      "maximum": 2147483647,
                      "x-nullable": true
                    },
                    "perEntryCreditCapMinutes": {
                      "type": "integer",
                      "minimum": -2147483648,
                      "maximum": 2147483647,
                      "default": null,
                      "x-nullable": true
                    },
                    "dailyCreditCapMinutes": {
                      "type": "integer",
                      "minimum": -2147483648,
                      "maximum": 2147483647,
                      "default": null,
                      "x-nullable": true
                    },
                    "creditBeyondTargetCapMinutes": {
                      "type": "integer",
                      "minimum": -2147483648,
                      "maximum": 2147483647,
                      "default": null,
                      "x-nullable": true
                    },
                    "unpaidFirstMinutes": {
                      "type": "integer",
                      "minimum": -2147483648,
                      "maximum": 2147483647,
                      "default": null,
                      "x-nullable": true
                    },
                    "worktimeFractionBp": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 10000,
                      "default": null,
                      "x-nullable": true
                    }
                  },
                  "required": [
                    "id",
                    "validFrom",
                    "validTo",
                    "countsAsWorkTime",
                    "accruesFlex",
                    "countsTowardMaxWorkTime",
                    "paid",
                    "maxDurationMinutes"
                  ]
                }
              }
            },
            "required": [
              "id",
              "code",
              "nameFi",
              "nameSv",
              "nameEn",
              "active",
              "isSystem",
              "locked",
              "sortOrder",
              "versions"
            ]
          }
        }
      },
      "required": ["entryTypes"]
    },
    "InvoicePageResponseDto": {
      "type": "object",
      "properties": {
        "items": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "documentType": {
                "type": "string",
                "enum": ["invoice", "credit_note", "reminder", "advance", "charge"]
              },
              "status": {
                "type": "string",
                "enum": ["draft", "issued"]
              },
              "lane": {
                "type": "string",
                "enum": [
                  "draft",
                  "approved",
                  "issued",
                  "sent",
                  "overdue",
                  "paid",
                  "written_off",
                  "credited",
                  "refund_due",
                  "no_payment_due"
                ]
              },
              "reviewStatus": {
                "type": "string",
                "enum": ["none", "requested", "approved", "returned"]
              },
              "reviewRequestedAt": {
                "type": "string",
                "x-nullable": true
              },
              "reviewRequestedByName": {
                "type": "string",
                "x-nullable": true
              },
              "reviewReturnedAt": {
                "type": "string",
                "x-nullable": true
              },
              "reviewReturnedByName": {
                "type": "string",
                "x-nullable": true
              },
              "reviewReturnedComment": {
                "type": "string",
                "x-nullable": true
              },
              "canApprove": {
                "type": "boolean"
              },
              "invoiceNumber": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991,
                "x-nullable": true
              },
              "invoiceNumberText": {
                "type": "string",
                "x-nullable": true
              },
              "customerId": {
                "type": "string"
              },
              "customerName": {
                "type": "string"
              },
              "invoiceDate": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                "x-nullable": true
              },
              "dueDate": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                "x-nullable": true
              },
              "paymentTermDays": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "periodFrom": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                "x-nullable": true
              },
              "periodTo": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                "x-nullable": true
              },
              "language": {
                "type": "string",
                "enum": ["fi", "sv", "en"]
              },
              "netTotalCents": {
                "type": "integer",
                "minimum": -2147483647,
                "maximum": 2147483647
              },
              "vatTotalCents": {
                "type": "integer",
                "minimum": -2147483647,
                "maximum": 2147483647
              },
              "grossTotalCents": {
                "type": "integer",
                "minimum": -2147483647,
                "maximum": 2147483647
              },
              "openCents": {
                "type": "integer",
                "minimum": -2147483647,
                "maximum": 2147483647
              },
              "creditLossCents": {
                "type": "integer",
                "minimum": -2147483647,
                "maximum": 2147483647
              },
              "overdueDays": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991,
                "x-nullable": true
              },
              "dispatchStatus": {
                "type": "string",
                "enum": ["queued", "sending", "sent", "delivered", "failed", "refused", null],
                "x-nullable": true
              },
              "dispatchChannel": {
                "type": "string",
                "enum": ["einvoice", "email", "manual", "print", null],
                "x-nullable": true
              },
              "dispatchTestMode": {
                "type": "boolean"
              },
              "effectiveDispatchRoute": {
                "type": "object",
                "properties": {
                  "channel": {
                    "type": "string",
                    "enum": ["einvoice", "email", "manual", "print"]
                  },
                  "recipient": {
                    "type": "string",
                    "x-nullable": true
                  },
                  "blockedCode": {
                    "type": "string",
                    "enum": [
                      "dispatch_recipient_missing",
                      "einvoice_address_missing",
                      "einvoice_not_supported_for_document",
                      "einvoice_operator_unsupported",
                      "einvoice_operator_not_configured",
                      "maventa_not_configured",
                      "apix_not_configured",
                      "einvoice_operator_not_connected",
                      "einvoice_operator_not_verified",
                      "einvoice_sender_address_missing",
                      "einvoice_print_service_disabled",
                      "reminder_original_settled",
                      null
                    ],
                    "x-nullable": true
                  }
                },
                "required": ["channel", "recipient", "blockedCode"]
              },
              "deliveredChannels": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": ["einvoice", "email", "manual", "print"]
                }
              },
              "invoiceEmail": {
                "type": "string",
                "x-nullable": true
              },
              "sentAt": {
                "type": "string",
                "x-nullable": true
              },
              "lastReminderAt": {
                "type": "string",
                "x-nullable": true
              },
              "originalInvoiceId": {
                "type": "string",
                "x-nullable": true
              },
              "originalInvoiceNumberText": {
                "type": "string",
                "x-nullable": true
              },
              "originalDocumentType": {
                "type": "string",
                "enum": ["invoice", "credit_note", "reminder", "advance", "charge", null],
                "default": null,
                "x-nullable": true
              },
              "creditedByInvoiceId": {
                "type": "string",
                "x-nullable": true
              },
              "lineCount": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "projectName": {
                "type": "string",
                "x-nullable": true
              },
              "createdAt": {
                "type": "string"
              },
              "updatedAt": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "documentType",
              "status",
              "lane",
              "reviewStatus",
              "reviewRequestedAt",
              "reviewRequestedByName",
              "reviewReturnedAt",
              "reviewReturnedByName",
              "reviewReturnedComment",
              "canApprove",
              "invoiceNumber",
              "invoiceNumberText",
              "customerId",
              "customerName",
              "invoiceDate",
              "dueDate",
              "paymentTermDays",
              "periodFrom",
              "periodTo",
              "language",
              "netTotalCents",
              "vatTotalCents",
              "grossTotalCents",
              "openCents",
              "overdueDays",
              "dispatchStatus",
              "dispatchChannel",
              "dispatchTestMode",
              "deliveredChannels",
              "invoiceEmail",
              "sentAt",
              "lastReminderAt",
              "originalInvoiceId",
              "originalInvoiceNumberText",
              "creditedByInvoiceId",
              "lineCount",
              "projectName",
              "createdAt",
              "updatedAt"
            ]
          }
        },
        "pageInfo": {
          "type": "object",
          "properties": {
            "nextCursor": {
              "type": "string",
              "x-nullable": true
            },
            "hasMore": {
              "type": "boolean"
            }
          },
          "required": ["nextCursor", "hasMore"]
        }
      },
      "required": ["items", "pageInfo"]
    },
    "LeadPageResponseDto": {
      "type": "object",
      "properties": {
        "items": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "companyName": {
                "type": "string",
                "x-nullable": true
              },
              "businessId": {
                "type": "string",
                "x-nullable": true
              },
              "personName": {
                "type": "string",
                "x-nullable": true
              },
              "email": {
                "type": "string",
                "x-nullable": true
              },
              "phone": {
                "type": "string",
                "x-nullable": true
              },
              "source": {
                "type": "string",
                "enum": ["manual", "intake", "import", "website", "referral", "other"]
              },
              "notes": {
                "type": "string",
                "x-nullable": true
              },
              "ownerMembershipId": {
                "type": "string",
                "x-nullable": true
              },
              "status": {
                "type": "string",
                "enum": ["new", "working", "converted", "discarded"]
              },
              "discardReason": {
                "type": "string",
                "x-nullable": true
              },
              "convertedDealId": {
                "type": "string",
                "x-nullable": true
              },
              "convertedCustomerId": {
                "type": "string",
                "x-nullable": true
              },
              "anonymizedAt": {
                "type": "string",
                "x-nullable": true
              },
              "possibleDuplicateOfCustomerId": {
                "type": "string",
                "x-nullable": true
              },
              "customFields": {
                "type": "object",
                "additionalProperties": {}
              },
              "createdAt": {
                "type": "string"
              },
              "updatedAt": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "title",
              "companyName",
              "businessId",
              "personName",
              "email",
              "phone",
              "source",
              "notes",
              "ownerMembershipId",
              "status",
              "discardReason",
              "convertedDealId",
              "convertedCustomerId",
              "anonymizedAt",
              "possibleDuplicateOfCustomerId",
              "customFields",
              "createdAt",
              "updatedAt"
            ]
          }
        },
        "pageInfo": {
          "type": "object",
          "properties": {
            "nextCursor": {
              "type": "string",
              "x-nullable": true
            },
            "hasMore": {
              "type": "boolean"
            }
          },
          "required": ["nextCursor", "hasMore"]
        }
      },
      "required": ["items", "pageInfo"]
    },
    "MembershipListResponseDto": {
      "type": "object",
      "properties": {
        "items": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "displayName": {
                "type": "string"
              },
              "firstNames": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              },
              "preferredName": {
                "type": "string",
                "x-nullable": true
              },
              "email": {
                "type": "string"
              },
              "role": {
                "type": "string",
                "enum": ["employee", "manager", "admin", "payroll", "hr"]
              },
              "managerMembershipId": {
                "type": "string",
                "x-nullable": true
              },
              "worktimeModelId": {
                "type": "string",
                "x-nullable": true
              },
              "planningUnitId": {
                "type": "string",
                "x-nullable": true
              },
              "canPlanAllUnits": {
                "type": "boolean"
              },
              "active": {
                "type": "boolean"
              },
              "employmentStartDate": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                "x-nullable": true
              },
              "employmentEndDate": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                "x-nullable": true
              },
              "personalFlexMinMinutes": {
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647,
                "x-nullable": true
              },
              "personalFlexMaxMinutes": {
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647,
                "x-nullable": true
              },
              "employer": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                },
                "required": ["id", "name"],
                "x-nullable": true
              },
              "invitationStatus": {
                "type": "string",
                "enum": ["pending", "accepted", "expired", "superseded", null],
                "x-nullable": true
              },
              "pendingAcceptance": {
                "type": "boolean"
              },
              "jobTitle": {
                "type": "string",
                "x-nullable": true
              },
              "employmentType": {
                "type": "string",
                "enum": ["permanent", "fixed_term", null],
                "x-nullable": true
              },
              "fixedTermEndDate": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                "x-nullable": true
              },
              "attention": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "statement_due",
                    "statement_update_due",
                    "probation_review",
                    "fixed_term_ending",
                    "qualification_expiring",
                    "qualification_expired",
                    "work_permit_expiring"
                  ]
                },
                "x-nullable": true
              },
              "updatedAt": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "displayName",
              "firstNames",
              "lastName",
              "preferredName",
              "email",
              "role",
              "managerMembershipId",
              "worktimeModelId",
              "planningUnitId",
              "canPlanAllUnits",
              "active",
              "employmentStartDate",
              "employmentEndDate",
              "personalFlexMinMinutes",
              "personalFlexMaxMinutes",
              "employer",
              "invitationStatus",
              "pendingAcceptance",
              "jobTitle",
              "employmentType",
              "fixedTermEndDate",
              "attention",
              "updatedAt"
            ]
          }
        },
        "pageInfo": {
          "type": "object",
          "properties": {
            "nextCursor": {
              "type": "string",
              "x-nullable": true
            },
            "hasMore": {
              "type": "boolean"
            }
          },
          "required": ["nextCursor", "hasMore"]
        },
        "totalCount": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        }
      },
      "required": ["items", "pageInfo"]
    },
    "Problem": {
      "type": "object",
      "description": "RFC 9457 problem document. Branch on `code`, never on `title` or `detail`: `code` is a stable machine-readable identifier, the human-readable fields exist for logs and may change.",
      "required": ["type", "title", "status", "code"],
      "properties": {
        "type": {
          "type": "string",
          "description": "Problem type URI. Points at the documentation anchor for `code` when the code is catalogued, otherwise `about:blank`.",
          "example": "https://kelomo.fi/developers/guides/error-codes/#code-read_only_key"
        },
        "title": {
          "type": "string",
          "description": "Short human-readable phrase for the status class."
        },
        "status": {
          "type": "integer",
          "description": "HTTP status code, repeated in the body."
        },
        "detail": {
          "type": "string",
          "x-nullable": true,
          "description": "Human-readable specifics for this occurrence. Never a translated message."
        },
        "code": {
          "type": "string",
          "description": "Stable machine-readable error identifier — the field to branch on. Catalogued at /developers/guides/error-codes/.",
          "example": "read_only_key"
        },
        "requestId": {
          "type": "string",
          "description": "Identifier of this request, mirrored in the `X-Request-Id` response header. Quote it in support requests."
        },
        "errors": {
          "type": "array",
          "description": "Per-FIELD validation failures on a 400: the input path and the rule it broke. Present only for schema refusals; capped at 20 entries.",
          "items": {
            "type": "object",
            "required": ["path", "code"],
            "properties": {
              "path": {
                "type": "string",
                "description": "Dot path of the refused field, e.g. `lines.0.quantity`."
              },
              "code": {
                "type": "string",
                "description": "The validation rule that failed, e.g. `too_big`."
              }
            }
          }
        },
        "feature": {
          "type": "string",
          "description": "The product module a 402 `feature_not_in_plan` names — the one to add to the plan."
        },
        "scope": {
          "type": "string",
          "description": "Whether a feature gate refused at organisation or at user scope."
        },
        "module": {
          "type": "string",
          "description": "The add-on module a 403 `module_not_active` names (licensed but switched off)."
        },
        "membershipId": {
          "type": "string",
          "description": "The member a per-row refusal is about."
        },
        "membershipIds": {
          "type": "array",
          "description": "The members a fail-closed refusal names; capped at 50.",
          "items": {
            "type": "string"
          }
        },
        "displayNames": {
          "type": "array",
          "description": "Names of the people named above, in the same order; capped at 50.",
          "items": {
            "type": "string"
          }
        },
        "customers": {
          "type": "array",
          "description": "Customers missing a prerequisite in the target system; capped at 50.",
          "items": {
            "type": "string"
          }
        },
        "buckets": {
          "type": "array",
          "description": "Export buckets missing a product code; capped at 50.",
          "items": {
            "type": "string"
          }
        },
        "rows": {
          "type": "array",
          "description": "The import rows a refusal names; capped at 50.",
          "items": {
            "type": "string"
          }
        },
        "days": {
          "type": "array",
          "description": "The person-days a refusal names — the payroll lock’s statutory-break findings (`payroll_run_break_findings`). Each entry pairs the member with the work date, so an acknowledgement can state what is being acknowledged; capped at 50.",
          "items": {
            "type": "object",
            "required": ["membershipId", "displayName", "date"],
            "properties": {
              "membershipId": {
                "type": "string",
                "description": "The member the finding is about."
              },
              "displayName": {
                "type": "string",
                "description": "That member’s name."
              },
              "date": {
                "type": "string",
                "description": "The work date, `YYYY-MM-DD`."
              }
            }
          }
        }
      }
    },
    "ProjectListResponseDto": {
      "type": "object",
      "properties": {
        "items": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "code": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "dimensionValueId": {
                "type": "string"
              },
              "customerId": {
                "type": "string",
                "x-nullable": true
              },
              "customerName": {
                "type": "string",
                "x-nullable": true
              },
              "billingCustomerId": {
                "type": "string",
                "x-nullable": true
              },
              "billingCustomerName": {
                "type": "string",
                "x-nullable": true
              },
              "contactId": {
                "type": "string",
                "x-nullable": true
              },
              "contactName": {
                "type": "string",
                "x-nullable": true
              },
              "ownerMembershipId": {
                "type": "string",
                "x-nullable": true
              },
              "ownerName": {
                "type": "string",
                "x-nullable": true
              },
              "status": {
                "type": "string",
                "enum": ["draft", "active", "on_hold", "closed"]
              },
              "billingModel": {
                "type": "string",
                "enum": ["time_materials", "fixed_price", "non_billable", "retainer"]
              },
              "defaultBillable": {
                "type": "boolean"
              },
              "startDate": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                "x-nullable": true
              },
              "endDate": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                "x-nullable": true
              },
              "budgetMinutes": {
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647,
                "x-nullable": true
              },
              "budgetAmountCents": {
                "type": "integer",
                "minimum": 0,
                "maximum": 2147483647,
                "x-nullable": true
              },
              "billingRoundingMinutes": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991,
                "x-nullable": true
              },
              "contractAmountCents": {
                "type": "integer",
                "minimum": 0,
                "maximum": 2147483647,
                "x-nullable": true
              },
              "contractReadyAt": {
                "type": "string",
                "x-nullable": true
              },
              "retainerQuotaMinutes": {
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647,
                "x-nullable": true
              },
              "purchaseBillingMultiplierPermille": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991,
                "x-nullable": true
              },
              "taskTimeTracking": {
                "type": "string",
                "enum": ["off", "optional", "required"]
              },
              "memberTaskVisibility": {
                "type": "string",
                "enum": ["assigned_only", "project_members"]
              },
              "taskAutoArchiveDays": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991,
                "x-nullable": true
              },
              "phaseBudgetAlertPct": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991,
                "x-nullable": true
              },
              "attributionPrecision": {
                "type": "string",
                "enum": ["project", "phase", "task"]
              },
              "externalSource": {
                "type": "string",
                "default": null,
                "x-nullable": true
              },
              "externalId": {
                "type": "string",
                "default": null,
                "x-nullable": true
              },
              "updatedAt": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "code",
              "name",
              "dimensionValueId",
              "customerId",
              "customerName",
              "billingCustomerId",
              "billingCustomerName",
              "contactId",
              "contactName",
              "ownerMembershipId",
              "ownerName",
              "status",
              "billingModel",
              "defaultBillable",
              "startDate",
              "endDate",
              "budgetMinutes",
              "budgetAmountCents",
              "billingRoundingMinutes",
              "contractAmountCents",
              "contractReadyAt",
              "retainerQuotaMinutes",
              "purchaseBillingMultiplierPermille",
              "taskTimeTracking",
              "memberTaskVisibility",
              "taskAutoArchiveDays",
              "phaseBudgetAlertPct",
              "attributionPrecision",
              "updatedAt"
            ]
          }
        },
        "pageInfo": {
          "type": "object",
          "properties": {
            "nextCursor": {
              "type": "string",
              "x-nullable": true
            },
            "hasMore": {
              "type": "boolean"
            }
          },
          "required": ["nextCursor", "hasMore"]
        }
      },
      "required": ["items", "pageInfo"]
    },
    "RejectAbsencePeriodDto": {
      "type": "object",
      "properties": {
        "reason": {
          "type": "string",
          "minLength": 3,
          "maxLength": 500
        },
        "expectedUpdatedAt": {
          "type": "string",
          "minLength": 1,
          "maxLength": 64,
          "description": "Optimistic lock: the `updatedAt` the editor was looking at, echoed back. A mismatch is 409 with the operation’s stale code. An empty string is not a version and is rejected."
        }
      },
      "required": ["reason"]
    },
    "ReturnWorkDayDto": {
      "type": "object",
      "properties": {
        "reason": {
          "type": "string",
          "minLength": 3,
          "maxLength": 500
        }
      },
      "required": ["reason"]
    },
    "SetWorkEntriesDto": {
      "type": "object",
      "properties": {
        "entries": {
          "minItems": 1,
          "maxItems": 20,
          "type": "array",
          "items": {
            "x-kelomo-null-as-absent": ["durationMinutes", "endMinutes", "note", "startMinutes"],
            "type": "object",
            "properties": {
              "entryTypeId": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              },
              "startMinutes": {
                "type": "integer",
                "minimum": 0,
                "maximum": 2879
              },
              "endMinutes": {
                "type": "integer",
                "minimum": 1,
                "maximum": 2880
              },
              "durationMinutes": {
                "type": "integer",
                "minimum": 1,
                "maximum": 1440
              },
              "note": {
                "type": "string",
                "maxLength": 200
              },
              "billable": {
                "type": "boolean"
              },
              "billingBaseMinutes": {
                "type": "integer",
                "minimum": 0,
                "maximum": 1440,
                "x-nullable": true
              },
              "allocations": {
                "default": [],
                "maxItems": 10,
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "dimensionId": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "valueId": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "weightBp": {
                      "default": 10000,
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 10000
                    },
                    "changeOrderId": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                      "x-nullable": true
                    }
                  },
                  "required": ["dimensionId", "valueId"]
                }
              },
              "taskId": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                "x-nullable": true
              },
              "sourceAbsencePeriodDayId": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                "x-nullable": true
              },
              "creditDayTypeId": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                "x-nullable": true
              }
            },
            "required": ["entryTypeId"]
          }
        },
        "breaks": {
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "startMinutes": {
                "type": "integer",
                "minimum": 0,
                "maximum": 2880
              },
              "endMinutes": {
                "type": "integer",
                "minimum": 1,
                "maximum": 2880
              },
              "breakTypeId": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                "x-nullable": true
              }
            },
            "required": ["startMinutes", "endMinutes"]
          }
        },
        "expectedUpdatedAt": {
          "type": "string",
          "minLength": 1,
          "maxLength": 64,
          "description": "Optimistic lock: the `updatedAt` the editor was looking at, echoed back. A mismatch is 409 with the operation’s stale code. An empty string is not a version and is rejected.",
          "x-nullable": true
        }
      },
      "required": ["entries"],
      "x-kelomo-read-only-ignored": [
        "absenceOutcome",
        "absencePeriod",
        "appliedCreditMinutes",
        "approverIsSelf",
        "attachmentCount",
        "attentionThresholdMinutes",
        "autoConfirmed",
        "balanceDeltaIfConfirmedMinutes",
        "balanceDeltaMinutes",
        "bankAccountTypeId",
        "bankCreditMinutes",
        "breakMinutes",
        "breakPolicy",
        "confidence",
        "confirmationOptional",
        "confirmedOnBehalf",
        "contentFingerprint",
        "creditedMinutes",
        "dayConfirmOpensAt",
        "dayOffReason",
        "dayType",
        "deviations",
        "effectiveSettings",
        "emergencyMinutes",
        "endMinutes",
        "evidenceHold",
        "expectedSource",
        "expectedWindow",
        "flexSurplusMinutes",
        "governingShiftType",
        "hasFlexWindow",
        "holidayCode",
        "holidayName",
        "noted",
        "openCorrectionRequest",
        "overtimeDisposition",
        "overtimeDispositionChosen",
        "overtimeMinutes",
        "paidBreakMinutes",
        "periodSettlesSurplus",
        "plannedFrame",
        "plannedShift",
        "plannedSpanEndsAt",
        "proposalDismissed",
        "proposedEntries",
        "rawDeltaMinutes",
        "recordedWorkMinutes",
        "replacedBy",
        "resolutionWindows",
        "returnReason",
        "returnRequested",
        "returnedAt",
        "returnedByName",
        "rosterPending",
        "runningBalanceMinutes",
        "separatingMinutes",
        "shiftTypeOverride",
        "shiftTypeOverrideInvalid",
        "shiftTypeOverrideMode",
        "shownBreakMinutes",
        "shownEndMinutes",
        "shownStartMinutes",
        "sources",
        "standby",
        "startMinutes",
        "status",
        "statutoryBasis",
        "supplements",
        "surplusMinutes",
        "targetMinutes",
        "uncountedMinutes",
        "uncreditedAbsenceMinutes",
        "updatedAt",
        "workDate",
        "workDayId",
        "workMinutes",
        "worktimeRegime"
      ],
      "additionalProperties": false
    },
    "TaskPageResponseDto": {
      "type": "object",
      "properties": {
        "items": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "projectId": {
                "type": "string"
              },
              "number": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "phaseId": {
                "type": "string",
                "x-nullable": true
              },
              "phaseName": {
                "type": "string",
                "x-nullable": true
              },
              "phaseSortOrder": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991,
                "x-nullable": true
              },
              "dimensionValueId": {
                "type": "string",
                "x-nullable": true
              },
              "title": {
                "type": "string"
              },
              "description": {
                "type": "string",
                "x-nullable": true
              },
              "statusId": {
                "type": "string"
              },
              "statusCategory": {
                "type": "string",
                "enum": ["todo", "in_progress", "done", "cancelled"]
              },
              "assigneeMembershipId": {
                "type": "string",
                "x-nullable": true
              },
              "assigneeName": {
                "type": "string",
                "x-nullable": true
              },
              "priority": {
                "type": "string",
                "enum": ["low", "normal", "high", "urgent"]
              },
              "startDate": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                "x-nullable": true
              },
              "dueDate": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                "x-nullable": true
              },
              "estimateMinutes": {
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647,
                "x-nullable": true
              },
              "progressPct": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100,
                "x-nullable": true
              },
              "sortKey": {
                "type": "string"
              },
              "milestoneId": {
                "type": "string",
                "x-nullable": true
              },
              "tags": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "color": {
                      "type": "string",
                      "x-nullable": true
                    }
                  },
                  "required": ["id", "name", "color"]
                }
              },
              "recurrenceId": {
                "type": "string",
                "x-nullable": true
              },
              "fieldValues": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "fieldId": {
                      "type": "string"
                    },
                    "value": {}
                  },
                  "required": ["fieldId", "value"]
                }
              },
              "archivedAt": {
                "type": "string",
                "x-nullable": true
              },
              "checklistDoneCount": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "checklistTotalCount": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "commentCount": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "actualMinutes": {
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647,
                "x-nullable": true
              },
              "externalSource": {
                "type": "string",
                "x-nullable": true
              },
              "externalId": {
                "type": "string",
                "x-nullable": true
              },
              "updatedAt": {
                "type": "string"
              },
              "createdAt": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "projectId",
              "number",
              "phaseId",
              "phaseName",
              "phaseSortOrder",
              "dimensionValueId",
              "title",
              "statusId",
              "statusCategory",
              "assigneeMembershipId",
              "assigneeName",
              "priority",
              "startDate",
              "dueDate",
              "estimateMinutes",
              "progressPct",
              "sortKey",
              "milestoneId",
              "tags",
              "recurrenceId",
              "fieldValues",
              "archivedAt",
              "checklistDoneCount",
              "checklistTotalCount",
              "commentCount",
              "actualMinutes",
              "externalSource",
              "externalId",
              "updatedAt",
              "createdAt"
            ]
          }
        },
        "pageInfo": {
          "type": "object",
          "properties": {
            "nextCursor": {
              "type": "string",
              "x-nullable": true
            },
            "hasMore": {
              "type": "boolean"
            }
          },
          "required": ["nextCursor", "hasMore"]
        }
      },
      "required": ["items", "pageInfo"]
    },
    "WebhookEventCatalogResponseDto": {
      "type": "object",
      "properties": {
        "events": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "event": {
                "type": "string",
                "enum": [
                  "quote.decided",
                  "batch.created",
                  "status_update.posted",
                  "deal.won",
                  "lead.created",
                  "workday.approved",
                  "export_batch.created",
                  "export_batch.delivered",
                  "export_batch.delivery_failed",
                  "absence.submitted",
                  "absence.decided",
                  "absence.changed",
                  "shift.published",
                  "invoice.issued",
                  "invoice.credited",
                  "invoice.dispatched",
                  "invoice.delivered",
                  "invoice.delivery_failed",
                  "payment.recorded",
                  "payment.allocated",
                  "payment.imported",
                  "invoice.overdue",
                  "invoice.drafts_ready",
                  "recurring_plan.run_completed",
                  "batch.delivered",
                  "workday.confirmed",
                  "workday.returned",
                  "workday.corrected",
                  "workday.unapproved",
                  "absence.cancelled",
                  "absence.certificate_provided",
                  "shift.changed",
                  "open_shift.claimed",
                  "open_shift.claim_decided",
                  "shift_swap.proposed",
                  "shift_swap.decided",
                  "export_batch.cancelled",
                  "payroll_period.locked",
                  "payroll_period.lock_lifted",
                  "member.created",
                  "member.invited",
                  "member.deactivated",
                  "member.deleted",
                  "employment.terminated",
                  "task.created",
                  "task.updated",
                  "task.status_changed",
                  "task.approval_responded",
                  "quote.sent",
                  "quote.accepted",
                  "deal.lost",
                  "deal.stage_changed",
                  "lead.converted",
                  "intake.received",
                  "change_order.approved",
                  "project.created",
                  "project.closed",
                  "contact.anonymized",
                  "lead.anonymized",
                  "invoice.paid",
                  "customer.created",
                  "customer.updated",
                  "project.updated",
                  "member.updated",
                  "shift.unpublished",
                  "activity.created",
                  "activity.completed",
                  "service_order.created",
                  "service_order.completed",
                  "service_visit.completed",
                  "service_report.signed",
                  "service_sla.breached",
                  "service_visit.scheduled",
                  "service_visit.rescheduled",
                  "service_visit.reassigned",
                  "trip.submitted",
                  "trip.approved",
                  "trip.returned",
                  "expense.submitted",
                  "expense.approved",
                  "expense.returned"
                ]
              },
              "capabilities": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "payloadSchema": {
                "type": "object",
                "additionalProperties": {}
              }
            },
            "required": ["event", "capabilities", "payloadSchema"]
          }
        }
      },
      "required": ["events"]
    },
    "WebhookOkResponseDto": {
      "type": "object",
      "properties": {
        "ok": {
          "type": "boolean"
        }
      },
      "required": ["ok"]
    },
    "WorkDayActionResponseDto": {
      "type": "object",
      "properties": {
        "status": {
          "type": "string",
          "enum": ["proposed", "draft", "confirmed", "approved", "exported"]
        },
        "warnings": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "params": {
                "default": {},
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            },
            "required": ["code"]
          }
        }
      },
      "required": ["status", "warnings"]
    },
    "WorkDayDetailResponseDto": {
      "type": "object",
      "properties": {
        "workDayId": {
          "type": "string",
          "format": "uuid",
          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
          "default": null,
          "x-nullable": true
        },
        "attachmentCount": {
          "default": 0,
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "plannedSpanEndsAt": {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
          "x-minimum-year": 1900,
          "x-maximum-year": 2999,
          "default": null,
          "x-nullable": true
        },
        "dayConfirmOpensAt": {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
          "x-minimum-year": 1900,
          "x-maximum-year": 2999,
          "x-nullable": true
        },
        "evidenceHold": {
          "type": "string",
          "enum": ["day_no_punches", "day_punch_auto_closed", null],
          "x-nullable": true
        },
        "workDate": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "status": {
          "type": "string",
          "enum": ["proposed", "draft", "confirmed", "approved", "exported"]
        },
        "startMinutes": {
          "type": "integer",
          "minimum": 0,
          "maximum": 1440,
          "x-nullable": true
        },
        "endMinutes": {
          "type": "integer",
          "minimum": 1,
          "maximum": 2880,
          "x-nullable": true
        },
        "breakMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "separatingMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "shownStartMinutes": {
          "type": "integer",
          "minimum": 0,
          "maximum": 2879,
          "x-nullable": true
        },
        "shownEndMinutes": {
          "type": "integer",
          "minimum": 1,
          "maximum": 2880,
          "x-nullable": true
        },
        "shownBreakMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "breaks": {
          "default": [],
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                "x-nullable": true
              },
              "startMinutes": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "endMinutes": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "breakTypeId": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                "x-nullable": true
              },
              "paidMinutes": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              }
            },
            "required": ["id", "startMinutes", "endMinutes", "breakTypeId", "paidMinutes"]
          }
        },
        "paidBreakMinutes": {
          "default": 0,
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "workMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "recordedWorkMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "creditedMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "targetMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "appliedCreditMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "uncreditedAbsenceMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "uncountedMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "rawDeltaMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "surplusMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "flexSurplusMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "bankCreditMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "balanceDeltaMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "balanceDeltaIfConfirmedMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "attentionThresholdMinutes": {
          "default": 15,
          "type": "integer",
          "minimum": 0,
          "maximum": 480
        },
        "worktimeRegime": {
          "type": "string",
          "enum": ["standard", "flexwork", "period"]
        },
        "hasFlexWindow": {
          "type": "boolean"
        },
        "periodSettlesSurplus": {
          "type": "boolean"
        },
        "runningBalanceMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "overtimeMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "dayType": {
          "type": "object",
          "properties": {
            "id": {
              "type": "string"
            },
            "code": {
              "type": "string",
              "pattern": "^[a-z][a-z0-9_]{1,49}$"
            },
            "nameFi": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "nameSv": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "nameEn": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            }
          },
          "required": ["id", "code", "nameFi", "nameSv", "nameEn"],
          "x-nullable": true
        },
        "absencePeriod": {
          "type": "object",
          "properties": {
            "id": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            },
            "status": {
              "type": "string",
              "enum": ["draft", "submitted", "approved", "rejected", "cancelled", "withdrawn"]
            },
            "workflowMode": {
              "type": "string",
              "enum": ["request", "notification", "employer_assignment"]
            },
            "nameFi": {
              "type": "string"
            },
            "nameSv": {
              "type": "string"
            },
            "nameEn": {
              "type": "string"
            },
            "absentMinutes": {
              "default": 0,
              "type": "integer",
              "minimum": -2147483648,
              "maximum": 2147483647
            },
            "absentStartMinutes": {
              "type": "integer",
              "minimum": 0,
              "maximum": 2879,
              "default": null,
              "x-nullable": true
            },
            "absentEndMinutes": {
              "type": "integer",
              "minimum": 1,
              "maximum": 2880,
              "default": null,
              "x-nullable": true
            },
            "startDate": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "endDate": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          "required": [
            "id",
            "status",
            "workflowMode",
            "nameFi",
            "nameSv",
            "nameEn",
            "startDate",
            "endDate"
          ],
          "x-nullable": true
        },
        "sources": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "worktime-model",
                  "calendar-event",
                  "planned-shift",
                  "punch",
                  "learned-baseline"
                ]
              },
              "versionId": {
                "type": "string"
              },
              "eventId": {
                "type": "string"
              },
              "shiftId": {
                "type": "string"
              },
              "punchSessionId": {
                "type": "string"
              },
              "weekday": {
                "type": "integer",
                "minimum": 1,
                "maximum": 7
              }
            },
            "required": ["type"]
          }
        },
        "deviations": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "overlong-day",
                  "short-day",
                  "missing-break",
                  "missing-second-break",
                  "fragmented-break",
                  "break-at-day-edge",
                  "flex-limit-exceeded",
                  "outside-flex-window",
                  "outside-allowed-window",
                  "work-on-day-off",
                  "entry-over-max-duration",
                  "impossible-clock-time",
                  "insufficient-daily-rest",
                  "insufficient-weekly-rest",
                  "punch-auto-closed",
                  "work-during-absence",
                  "shift-on-empty-day",
                  "young-worker-hours",
                  "young-worker-daily-work",
                  "young-worker-annual-overtime"
                ]
              },
              "deltaMinutes": {
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647
              },
              "severity": {
                "type": "string",
                "enum": ["advisory", "info"]
              },
              "restMinutes": {
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647
              },
              "requiredMinutes": {
                "type": "integer",
                "minimum": -2147483648,
                "maximum": 2147483647
              },
              "previousEndDate": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "previousEndMinutes": {
                "type": "integer",
                "minimum": 1,
                "maximum": 2880
              }
            },
            "required": ["type", "deltaMinutes"]
          }
        },
        "overtimeDisposition": {
          "default": "flex",
          "type": "string",
          "enum": ["flex", "payout", "ignore", "bank"]
        },
        "overtimeDispositionChosen": {
          "type": "boolean"
        },
        "bankAccountTypeId": {
          "type": "string",
          "default": null,
          "x-nullable": true
        },
        "entries": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "entryTypeId": {
                "type": "string"
              },
              "entryTypeCode": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9_]{1,49}$"
              },
              "startMinutes": {
                "type": "integer",
                "minimum": 0,
                "maximum": 2879,
                "x-nullable": true
              },
              "endMinutes": {
                "type": "integer",
                "minimum": 1,
                "maximum": 2880,
                "x-nullable": true
              },
              "durationMinutes": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991,
                "default": null,
                "x-nullable": true
              },
              "elapsedMinutes": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991,
                "default": null,
                "x-nullable": true
              },
              "note": {
                "type": "string",
                "x-nullable": true
              },
              "billable": {
                "type": "boolean"
              },
              "billingBaseMinutes": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991,
                "default": null,
                "x-nullable": true
              },
              "allocations": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "dimensionId": {
                      "type": "string"
                    },
                    "valueId": {
                      "type": "string"
                    },
                    "valueCode": {
                      "type": "string"
                    },
                    "valueName": {
                      "type": "string"
                    },
                    "weightBp": {
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "changeOrderId": {
                      "type": "string",
                      "x-nullable": true
                    },
                    "changeOrderLabel": {
                      "type": "string",
                      "x-nullable": true
                    }
                  },
                  "required": ["dimensionId", "valueId", "valueCode", "valueName", "weightBp"]
                }
              },
              "sourceAbsencePeriodDayId": {
                "type": "string",
                "default": null,
                "x-nullable": true
              },
              "creditDayTypeId": {
                "type": "string",
                "default": null,
                "x-nullable": true
              },
              "punched": {
                "default": false,
                "type": "boolean"
              }
            },
            "required": [
              "id",
              "entryTypeId",
              "entryTypeCode",
              "startMinutes",
              "endMinutes",
              "note",
              "billable",
              "allocations"
            ]
          }
        },
        "proposedEntries": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "startMinutes": {
                "type": "integer",
                "minimum": 0,
                "maximum": 1440
              },
              "endMinutes": {
                "type": "integer",
                "minimum": 1,
                "maximum": 2880
              },
              "elapsedMinutes": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991,
                "default": null,
                "x-nullable": true
              },
              "entryTypeId": {
                "type": "string"
              },
              "billable": {
                "type": "boolean"
              },
              "allocations": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "dimensionId": {
                      "type": "string"
                    },
                    "valueId": {
                      "type": "string"
                    },
                    "valueCode": {
                      "type": "string"
                    },
                    "valueName": {
                      "type": "string"
                    },
                    "weightBp": {
                      "default": 10000,
                      "type": "integer",
                      "minimum": -9007199254740991,
                      "maximum": 9007199254740991
                    },
                    "source": {
                      "type": "string",
                      "enum": ["calendar", "geofence", "task", "baseline", "default"]
                    },
                    "confidence": {
                      "type": "string",
                      "enum": ["high", "medium", "low"]
                    }
                  },
                  "required": [
                    "dimensionId",
                    "valueId",
                    "valueCode",
                    "valueName",
                    "source",
                    "confidence"
                  ]
                }
              },
              "confidence": {
                "type": "string",
                "enum": ["high", "medium", "low"]
              }
            },
            "required": [
              "startMinutes",
              "endMinutes",
              "entryTypeId",
              "billable",
              "allocations",
              "confidence"
            ]
          }
        },
        "expectedSource": {
          "type": "string",
          "x-nullable": true
        },
        "expectedWindow": {
          "type": "object",
          "properties": {
            "startMinutes": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1440
            },
            "endMinutes": {
              "type": "integer",
              "minimum": 1,
              "maximum": 2880
            },
            "breakMinutes": {
              "type": "integer",
              "minimum": 0,
              "maximum": 2147483647
            },
            "break": {
              "type": "object",
              "properties": {
                "startMinutes": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 2880
                },
                "endMinutes": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 2880
                }
              },
              "required": ["startMinutes", "endMinutes"],
              "x-nullable": true
            }
          },
          "required": ["startMinutes", "endMinutes", "breakMinutes"],
          "x-nullable": true
        },
        "proposalDismissed": {
          "type": "boolean"
        },
        "rosterPending": {
          "type": "boolean"
        },
        "updatedAt": {
          "type": "string",
          "default": null,
          "x-nullable": true
        },
        "contentFingerprint": {
          "type": "string"
        },
        "breakPolicy": {
          "type": "object",
          "properties": {
            "mode": {
              "type": "string",
              "enum": ["none", "minimum", "fixed"]
            },
            "thresholdMinutes": {
              "type": "integer",
              "minimum": -9007199254740991,
              "maximum": 9007199254740991
            },
            "thresholdBasis": {
              "type": "string",
              "enum": ["work", "span"]
            },
            "breakMinutes": {
              "type": "integer",
              "minimum": -2147483648,
              "maximum": 2147483647
            },
            "mealCountsAsWorkTime": {
              "type": "boolean"
            },
            "extendShortBreakToMinutes": {
              "type": "integer",
              "minimum": -2147483648,
              "maximum": 2147483647
            },
            "shortenLongBreakToMinutes": {
              "type": "integer",
              "minimum": -2147483648,
              "maximum": 2147483647,
              "x-nullable": true
            },
            "zeroBreakSuppressesAutoDeduct": {
              "type": "boolean"
            },
            "workTimeBreak": {
              "type": "object",
              "properties": {
                "thresholdMinutes": {
                  "type": "integer",
                  "minimum": -2147483648,
                  "maximum": 2147483647
                },
                "requiredBreakMinutes": {
                  "type": "integer",
                  "minimum": -2147483648,
                  "maximum": 2147483647
                }
              },
              "required": ["thresholdMinutes", "requiredBreakMinutes"],
              "x-nullable": true
            }
          },
          "required": [
            "mode",
            "thresholdMinutes",
            "thresholdBasis",
            "breakMinutes",
            "mealCountsAsWorkTime",
            "extendShortBreakToMinutes",
            "shortenLongBreakToMinutes",
            "zeroBreakSuppressesAutoDeduct"
          ]
        },
        "returnReason": {
          "type": "string",
          "x-nullable": true
        },
        "returnedAt": {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
          "x-minimum-year": 1900,
          "x-maximum-year": 2999,
          "x-nullable": true
        },
        "returnedByName": {
          "type": "string",
          "x-nullable": true
        },
        "returnRequested": {
          "type": "boolean"
        },
        "replacedBy": {
          "type": "object",
          "properties": {
            "kind": {
              "type": "object",
              "properties": {
                "nameFi": {
                  "type": "string"
                },
                "nameSv": {
                  "type": "string"
                },
                "nameEn": {
                  "type": "string"
                }
              },
              "required": ["nameFi", "nameSv", "nameEn"]
            },
            "byName": {
              "type": "string",
              "x-nullable": true
            },
            "at": {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
              "x-minimum-year": 1900,
              "x-maximum-year": 2999
            },
            "original": {
              "type": "object",
              "properties": {
                "startMinutes": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 2879,
                  "x-nullable": true
                },
                "endMinutes": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 2880,
                  "x-nullable": true
                },
                "entries": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "entryTypeId": {
                        "type": "string"
                      },
                      "entryTypeCode": {
                        "type": "string",
                        "pattern": "^[a-z][a-z0-9_]{1,49}$"
                      },
                      "startMinutes": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 2879,
                        "x-nullable": true
                      },
                      "endMinutes": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 2880,
                        "x-nullable": true
                      },
                      "durationMinutes": {
                        "type": "integer",
                        "minimum": -9007199254740991,
                        "maximum": 9007199254740991,
                        "x-nullable": true
                      },
                      "elapsedMinutes": {
                        "type": "integer",
                        "minimum": -9007199254740991,
                        "maximum": 9007199254740991
                      }
                    },
                    "required": [
                      "entryTypeId",
                      "entryTypeCode",
                      "startMinutes",
                      "endMinutes",
                      "durationMinutes",
                      "elapsedMinutes"
                    ]
                  }
                }
              },
              "required": ["startMinutes", "endMinutes", "entries"],
              "x-nullable": true
            }
          },
          "required": ["kind", "byName", "at", "original"]
        },
        "confirmationOptional": {
          "type": "boolean"
        },
        "confirmedOnBehalf": {
          "type": "boolean"
        },
        "autoConfirmed": {
          "type": "boolean"
        },
        "confidence": {
          "type": "object",
          "properties": {
            "level": {
              "type": "string",
              "enum": ["high", "medium", "low"]
            },
            "reasons": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "required": ["level", "reasons"]
        },
        "supplements": {
          "default": [],
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "supplementTypeId": {
                "type": "string"
              },
              "supplementTypeCode": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9_]{1,49}$"
              },
              "nameFi": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100
              },
              "nameSv": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100
              },
              "nameEn": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100
              },
              "valueKind": {
                "type": "string",
                "enum": ["flag", "count", "hours", "amount"]
              },
              "unit": {
                "type": "string",
                "enum": ["hours", "pieces", "days", "decimal"]
              },
              "exportMode": {
                "type": "string",
                "enum": ["passthrough", "tesOnly"]
              },
              "quantity": {
                "type": "number"
              },
              "startMinutes": {
                "type": "integer",
                "minimum": 0,
                "maximum": 1440,
                "x-nullable": true
              },
              "endMinutes": {
                "type": "integer",
                "minimum": 0,
                "maximum": 1440,
                "x-nullable": true
              },
              "variantCode": {
                "type": "string",
                "x-nullable": true
              },
              "comment": {
                "type": "string",
                "x-nullable": true
              }
            },
            "required": [
              "id",
              "supplementTypeId",
              "supplementTypeCode",
              "nameFi",
              "nameSv",
              "nameEn",
              "valueKind",
              "unit",
              "exportMode",
              "quantity",
              "startMinutes",
              "endMinutes",
              "variantCode",
              "comment"
            ]
          }
        },
        "standby": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "periodId": {
                "type": "string"
              },
              "standbyType": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "code": {
                    "type": "string",
                    "pattern": "^[a-z][a-z0-9_]{1,49}$"
                  },
                  "nameFi": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "nameSv": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "nameEn": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "worktimeFraction": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  }
                },
                "required": ["id", "code", "nameFi", "nameSv", "nameEn", "worktimeFraction"]
              },
              "startMinutes": {
                "type": "integer",
                "minimum": 0,
                "maximum": 1440
              },
              "endMinutes": {
                "type": "integer",
                "minimum": 1,
                "maximum": 1440
              }
            },
            "required": ["periodId", "standbyType", "startMinutes", "endMinutes"]
          }
        },
        "governingShiftType": {
          "type": "object",
          "properties": {
            "id": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            },
            "nameFi": {
              "type": "string"
            },
            "nameSv": {
              "type": "string"
            },
            "nameEn": {
              "type": "string"
            },
            "shortCode": {
              "type": "string"
            },
            "color": {
              "type": "string"
            },
            "source": {
              "type": "string",
              "enum": ["plan", "walk-up", "rule", "model-default", "override"]
            }
          },
          "required": ["nameFi", "nameSv", "nameEn", "shortCode", "color"]
        },
        "plannedShift": {
          "type": "object",
          "properties": {
            "startMinutes": {
              "type": "integer",
              "minimum": -9007199254740991,
              "maximum": 9007199254740991,
              "x-nullable": true
            },
            "endMinutes": {
              "type": "integer",
              "minimum": -9007199254740991,
              "maximum": 9007199254740991,
              "x-nullable": true
            },
            "breakMinutes": {
              "type": "integer",
              "minimum": -2147483648,
              "maximum": 2147483647
            },
            "targetMinutes": {
              "type": "integer",
              "minimum": -2147483648,
              "maximum": 2147483647
            },
            "source": {
              "type": "string",
              "enum": ["person-shift", "group-shift"]
            },
            "shiftType": {
              "type": "object",
              "properties": {
                "nameFi": {
                  "type": "string"
                },
                "nameSv": {
                  "type": "string"
                },
                "nameEn": {
                  "type": "string"
                },
                "shortCode": {
                  "type": "string"
                },
                "color": {
                  "type": "string"
                }
              },
              "required": ["nameFi", "nameSv", "nameEn", "shortCode", "color"],
              "x-nullable": true
            }
          },
          "required": [
            "startMinutes",
            "endMinutes",
            "breakMinutes",
            "targetMinutes",
            "source",
            "shiftType"
          ]
        },
        "plannedFrame": {
          "type": "object",
          "properties": {
            "startMinutes": {
              "type": "integer",
              "minimum": -9007199254740991,
              "maximum": 9007199254740991
            },
            "endMinutes": {
              "type": "integer",
              "minimum": -9007199254740991,
              "maximum": 9007199254740991
            }
          },
          "required": ["startMinutes", "endMinutes"]
        },
        "dayOffReason": {
          "type": "string",
          "enum": [
            "weekend",
            "holiday",
            "model",
            "roster",
            "shift",
            "outside-employment",
            "before-tracking",
            "no-model",
            "no-model-version",
            null
          ],
          "x-nullable": true
        },
        "holidayName": {
          "type": "object",
          "properties": {
            "fi": {
              "type": "string"
            },
            "sv": {
              "type": "string"
            },
            "en": {
              "type": "string"
            }
          },
          "required": ["fi", "sv", "en"],
          "x-nullable": true
        },
        "holidayCode": {
          "type": "string",
          "x-nullable": true
        },
        "effectiveSettings": {
          "type": "object",
          "properties": {
            "breakDeductionMode": {
              "type": "string",
              "enum": ["none", "minimum", "fixed"]
            },
            "breakThresholdMinutes": {
              "type": "number"
            },
            "breakThresholdBasis": {
              "type": "string",
              "enum": ["work", "span"]
            },
            "minBreakMinutes": {
              "type": "number"
            },
            "paidCoffeeTotalMinutes": {
              "type": "number"
            },
            "paidCoffeeMaxCount": {
              "type": "number"
            },
            "flexWindowStartMinutes": {
              "type": "number",
              "x-nullable": true
            },
            "flexWindowEndMinutes": {
              "type": "number",
              "x-nullable": true
            },
            "dayFlexCapMinutes": {
              "type": "number",
              "x-nullable": true
            },
            "dayFlexFloorMinutes": {
              "type": "number",
              "x-nullable": true
            },
            "dailyRegularBasis": {
              "type": "string",
              "enum": ["target", "fixed"]
            },
            "dailyRegularFixedMinutes": {
              "type": "number",
              "x-nullable": true
            },
            "dailyOt50Minutes": {
              "type": "number",
              "x-nullable": true
            },
            "flexRoundingMinutes": {
              "type": "number"
            },
            "openPunchAutoClose": {
              "type": "string",
              "enum": ["off", "boundary", "planned_end"]
            },
            "punchRoundingMinutes": {
              "type": "number"
            },
            "punchGraceStartMinutes": {
              "type": "number"
            },
            "punchGraceEndMinutes": {
              "type": "number"
            },
            "trace": {
              "type": "object",
              "additionalProperties": {
                "type": "string"
              }
            }
          },
          "required": [
            "breakDeductionMode",
            "breakThresholdMinutes",
            "breakThresholdBasis",
            "minBreakMinutes",
            "paidCoffeeTotalMinutes",
            "paidCoffeeMaxCount",
            "flexWindowStartMinutes",
            "flexWindowEndMinutes",
            "dayFlexCapMinutes",
            "dayFlexFloorMinutes",
            "dailyRegularBasis",
            "dailyRegularFixedMinutes",
            "dailyOt50Minutes",
            "flexRoundingMinutes",
            "openPunchAutoClose",
            "punchRoundingMinutes",
            "punchGraceStartMinutes",
            "punchGraceEndMinutes",
            "trace"
          ]
        },
        "statutoryBasis": {
          "type": "boolean"
        },
        "approverIsSelf": {
          "type": "boolean"
        },
        "emergencyMinutes": {
          "type": "integer",
          "minimum": -2147483648,
          "maximum": 2147483647
        },
        "openCorrectionRequest": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string"
            },
            "by": {
              "type": "string"
            },
            "requestedAt": {
              "type": "string"
            }
          },
          "required": ["reason", "by", "requestedAt"],
          "default": null,
          "x-nullable": true
        },
        "shiftTypeOverride": {
          "type": "object",
          "properties": {
            "shiftTypeId": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          },
          "required": ["shiftTypeId"],
          "default": null,
          "x-nullable": true
        },
        "shiftTypeOverrideInvalid": {
          "type": "boolean"
        },
        "shiftTypeOverrideMode": {
          "type": "string",
          "enum": ["off", "manager", "employee"]
        },
        "resolutionWindows": {
          "default": [],
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "shiftTypeId": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              },
              "nameFi": {
                "type": "string"
              },
              "nameSv": {
                "type": "string"
              },
              "nameEn": {
                "type": "string"
              },
              "shortCode": {
                "type": "string"
              },
              "color": {
                "type": "string"
              },
              "startMinutes": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "endMinutes": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "priority": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              }
            },
            "required": [
              "shiftTypeId",
              "nameFi",
              "nameSv",
              "nameEn",
              "shortCode",
              "color",
              "startMinutes",
              "endMinutes",
              "priority"
            ]
          }
        },
        "absenceOutcome": {
          "type": "object",
          "properties": {
            "status": {
              "type": "string",
              "enum": ["created", "pending", "not_created"]
            },
            "code": {
              "type": "string",
              "default": null,
              "x-nullable": true
            },
            "absentStartMinutes": {
              "type": "integer",
              "minimum": 0,
              "maximum": 2879,
              "default": null,
              "x-nullable": true
            },
            "absentEndMinutes": {
              "type": "integer",
              "minimum": 1,
              "maximum": 2880,
              "default": null,
              "x-nullable": true
            },
            "absentMinutes": {
              "type": "integer",
              "minimum": -2147483648,
              "maximum": 2147483647,
              "default": null,
              "x-nullable": true
            },
            "periodId": {
              "type": "string",
              "default": null,
              "x-nullable": true
            },
            "policyNameFi": {
              "type": "string",
              "default": null,
              "x-nullable": true
            },
            "policyNameSv": {
              "type": "string",
              "default": null,
              "x-nullable": true
            },
            "policyNameEn": {
              "type": "string",
              "default": null,
              "x-nullable": true
            }
          },
          "required": ["status"],
          "default": null,
          "x-nullable": true
        },
        "noted": {
          "type": "object",
          "properties": {
            "code": {
              "type": "string"
            },
            "occurredAt": {
              "type": "string"
            }
          },
          "required": ["code", "occurredAt"],
          "default": null,
          "x-nullable": true
        }
      },
      "required": [
        "workDate",
        "status",
        "startMinutes",
        "endMinutes",
        "breakMinutes",
        "shownStartMinutes",
        "shownEndMinutes",
        "shownBreakMinutes",
        "workMinutes",
        "recordedWorkMinutes",
        "creditedMinutes",
        "targetMinutes",
        "balanceDeltaMinutes",
        "runningBalanceMinutes",
        "overtimeMinutes",
        "dayType",
        "sources",
        "deviations",
        "entries",
        "expectedSource"
      ]
    }
  },
  "securityDefinitions": {
    "bearer": {
      "type": "apiKey",
      "in": "header",
      "name": "Authorization",
      "description": "API key sent as a bearer token: `Bearer kelomo_…`"
    }
  },
  "security": [
    {
      "bearer": []
    }
  ],
  "x-ms-connector-metadata": [
    {
      "propertyName": "Website",
      "propertyValue": "https://kelomo.fi"
    },
    {
      "propertyName": "Privacy policy",
      "propertyValue": "https://kelomo.fi/tietosuoja/"
    },
    {
      "propertyName": "Categories",
      "propertyValue": "Productivity;Business Management"
    }
  ]
}
