{
  "openapi": "3.0.3",
  "info": {
    "title": "inshift API",
    "version": "1.0.0",
    "description": "People, locations, positions, the schedule, absences, attendance, the timesheet and the payroll of an inshift organization. Reading is the whole of v1 except the schedule and the staff list, which can also be written.\n\n**Writing.** Every POST carries `Idempotency-Key`, every edit and delete carries `If-Match` with the version you last read. Publishing a shift is a separate scope from writing one: an integration can leave drafts without being able to show them to the team. A shift that does reach people notifies them exactly as if a manager had put it there.\n\n**Authentication.** `Authorization: Bearer insh_live_...`. A key belongs to exactly one organization and can never reach another one; there is no `organization_id` parameter anywhere in this API, by design.\n\n**Money is read-only and behind its own scope.** `payroll:read` opens accruals with every supplement explained, payouts, balances, pay runs and the pay configuration. Nothing about money can be written through the API. Every amount is a string with two decimals.\n\n**Every response carries `Inshift-Request-Id`.** Quote it when asking us about a request; without it a failure cannot be found in our logs.",
    "contact": {
      "name": "inshift",
      "url": "https://inshift.app"
    }
  },
  "servers": [
    {
      "url": "https://api.inshift.app",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "company",
      "description": "The organization itself"
    },
    {
      "name": "locations",
      "description": "Locations"
    },
    {
      "name": "positions",
      "description": "Job positions"
    },
    {
      "name": "employees",
      "description": "People"
    },
    {
      "name": "shifts",
      "description": "The schedule"
    },
    {
      "name": "absences",
      "description": "Vacation, sick leave, days off"
    },
    {
      "name": "attendance",
      "description": "Clock-in records"
    },
    {
      "name": "timesheets",
      "description": "Hours per person per day"
    },
    {
      "name": "events",
      "description": "Change feed for incremental sync"
    },
    {
      "name": "payroll",
      "description": "Accruals, balances, pay runs and the pay configuration"
    },
    {
      "name": "payments",
      "description": "Payouts to people"
    }
  ],
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "authentication_error",
                  "permission_error",
                  "invalid_request_error",
                  "not_found",
                  "conflict",
                  "rate_limit_error",
                  "api_error",
                  "service_unavailable"
                ]
              },
              "code": {
                "type": "string",
                "description": "A stable string. Branch on this, never on message"
              },
              "message": {
                "type": "string"
              },
              "param": {
                "nullable": true,
                "type": "string"
              },
              "doc_url": {
                "type": "string"
              },
              "request_id": {
                "type": "string"
              }
            },
            "required": [
              "type",
              "code",
              "message",
              "param",
              "doc_url",
              "request_id"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "id": "Error"
      },
      "Company": {
        "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)$"
          },
          "name": {
            "type": "string"
          },
          "timezone": {
            "type": "string",
            "description": "The organization time zone. Every wall-clock time in this API is read in it"
          },
          "currency": {
            "nullable": true,
            "description": "The currency payroll is kept in. v1 returns no amounts anywhere",
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          },
          "features": {
            "type": "object",
            "properties": {
              "attendance": {
                "type": "boolean",
                "description": "Attendance tracking is on. When off, /v1/attendance is always empty, and that is the module being off, not nobody working"
              },
              "open_shifts": {
                "type": "boolean"
              }
            },
            "required": [
              "attendance",
              "open_shifts"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "id",
          "name",
          "timezone",
          "currency",
          "created_at",
          "features"
        ],
        "additionalProperties": false,
        "id": "Company"
      },
      "Location": {
        "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)$"
          },
          "name": {
            "type": "string"
          },
          "address": {
            "nullable": true,
            "type": "string"
          },
          "external_id": {
            "nullable": true,
            "type": "string"
          },
          "group": {
            "nullable": true,
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "archived"
            ],
            "description": "Locations are archived, never deleted: past schedules must stay readable"
          },
          "created_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          }
        },
        "required": [
          "id",
          "name",
          "address",
          "external_id",
          "group",
          "status",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "id": "Location"
      },
      "Position": {
        "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)$"
          },
          "name": {
            "type": "string"
          },
          "external_id": {
            "nullable": true,
            "type": "string"
          },
          "group": {
            "nullable": true,
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          }
        },
        "required": [
          "id",
          "name",
          "external_id",
          "group",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "id": "Position",
        "description": "A job position (\"Barista\"), not an access role"
      },
      "Employee": {
        "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)$"
          },
          "external_id": {
            "nullable": true,
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "dismissed"
            ]
          },
          "in_schedule": {
            "type": "boolean",
            "description": "This person is scheduled. false means they are on the team but never appear in the schedule"
          },
          "hire_date": {
            "nullable": true,
            "type": "string",
            "description": "A calendar date, YYYY-MM-DD"
          },
          "dismissed_at": {
            "nullable": true,
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          },
          "position_id": {
            "nullable": true,
            "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)$"
          },
          "position_ids": {
            "type": "array",
            "items": {
              "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)$"
            }
          },
          "primary_location_id": {
            "nullable": true,
            "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)$"
          },
          "location_ids": {
            "type": "array",
            "items": {
              "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)$"
            }
          },
          "all_locations": {
            "type": "boolean",
            "description": "Works at every location. location_ids then lists only explicit attachments and limits nothing"
          },
          "has_account": {
            "type": "boolean",
            "description": "The person has joined the app with an invite code and signs in with their own account. false means nobody has joined under this card yet: an invite code (POST /v1/employees/{id}/invite-code) is what connects them"
          },
          "is_intern": {
            "type": "boolean",
            "description": "An intern: works alongside a mentor and is paid the intern rate of their position. Always false while the Internships module is off"
          },
          "hire_planned_at": {
            "nullable": true,
            "description": "When an intern is planned to join the staff; null when nothing is planned. It happens by itself at that moment",
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          },
          "personnel_no": {
            "nullable": true,
            "type": "string"
          },
          "phone": {
            "nullable": true,
            "type": "string"
          },
          "birth_date": {
            "nullable": true,
            "type": "string",
            "description": "A calendar date, YYYY-MM-DD"
          },
          "email": {
            "description": "The ACCOUNT email. null means the person has no account yet, NOT that the email is blank",
            "nullable": true,
            "type": "string"
          }
        },
        "required": [
          "id",
          "external_id",
          "name",
          "status",
          "in_schedule",
          "hire_date",
          "dismissed_at",
          "position_id",
          "position_ids",
          "primary_location_id",
          "location_ids",
          "all_locations",
          "has_account",
          "is_intern",
          "hire_planned_at",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "id": "Employee"
      },
      "Shift": {
        "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)$"
          },
          "date": {
            "type": "string",
            "description": "A calendar date, YYYY-MM-DD"
          },
          "start_time": {
            "type": "string",
            "description": "Wall-clock time as the schedule shows it, HH:MM:SS"
          },
          "end_time": {
            "type": "string"
          },
          "start_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          },
          "end_at": {
            "type": "string",
            "description": "For an overnight shift this is already the next day"
          },
          "hours": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Hours as a string with two decimals. A string, so no float rounding is introduced on the way"
          },
          "employee_id": {
            "nullable": true,
            "description": "null means an unfilled slot",
            "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)$"
          },
          "employee_name": {
            "nullable": true,
            "description": "The name of the person on the shift, so a list of shifts reads without a lookup per row. null for an unfilled slot",
            "type": "string"
          },
          "location_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)$"
          },
          "location_name": {
            "type": "string"
          },
          "position_id": {
            "nullable": true,
            "description": "The position of the shift itself, which is not the same thing as the position of the person. An unfilled slot uses it to say which position it is looking for; a slot that was filled from the open list keeps the position it was taken with. Most shifts are planned by putting a named person on a day, and those carry null: nothing about the shift overrides anything, and the position to show is the person own, from employees.position_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",
              "published",
              "confirmed"
            ]
          },
          "open": {
            "type": "boolean",
            "description": "An unfilled slot offered to the team: people apply, a manager picks"
          },
          "is_substitution": {
            "type": "boolean"
          },
          "label": {
            "nullable": true,
            "type": "string"
          },
          "version": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Optimistic-locking token. Will be used with If-Match once writes arrive"
          },
          "edited": {
            "type": "boolean",
            "description": "The shift was edited after it was created"
          },
          "created_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          }
        },
        "required": [
          "id",
          "date",
          "start_time",
          "end_time",
          "start_at",
          "end_at",
          "hours",
          "employee_id",
          "employee_name",
          "location_id",
          "location_name",
          "position_id",
          "status",
          "open",
          "is_substitution",
          "label",
          "version",
          "edited",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "id": "Shift"
      },
      "ShiftCreate": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "start_time": {
            "type": "string",
            "pattern": "^\\d{2}:\\d{2}(:\\d{2})?$"
          },
          "end_time": {
            "type": "string",
            "pattern": "^\\d{2}:\\d{2}(:\\d{2})?$"
          },
          "location_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)$"
          },
          "employee_id": {
            "nullable": true,
            "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)$"
          },
          "position_id": {
            "nullable": true,
            "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",
              "published"
            ]
          },
          "label": {
            "nullable": true,
            "type": "string",
            "maxLength": 50
          },
          "open": {
            "type": "boolean"
          },
          "is_substitution": {
            "type": "boolean"
          }
        },
        "required": [
          "date",
          "start_time",
          "end_time",
          "location_id"
        ],
        "additionalProperties": false,
        "id": "ShiftCreate",
        "description": "A new shift. Without `status` it is a draft, and a draft is visible to nobody but the people who build the schedule."
      },
      "ShiftBatchResult": {
        "type": "object",
        "properties": {
          "created": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "failed": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": {
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991,
                  "description": "Position in the shifts array you sent"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "created",
                    "failed"
                  ]
                },
                "shift": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Shift"
                    }
                  ]
                },
                "error": {
                  "type": "object",
                  "properties": {
                    "type": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "param": {
                      "nullable": true,
                      "type": "string"
                    },
                    "doc_url": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "type",
                    "code",
                    "message",
                    "param",
                    "doc_url"
                  ],
                  "additionalProperties": false
                }
              },
              "required": [
                "index",
                "status"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "created",
          "failed",
          "results"
        ],
        "additionalProperties": false,
        "id": "ShiftBatchResult"
      },
      "ShiftBatchCreate": {
        "type": "object",
        "properties": {
          "shifts": {
            "minItems": 1,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ShiftCreate"
            }
          }
        },
        "required": [
          "shifts"
        ],
        "additionalProperties": false,
        "id": "ShiftBatchCreate",
        "description": "Up to 500 shifts in one request."
      },
      "ShiftPatch": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "start_time": {
            "type": "string",
            "pattern": "^\\d{2}:\\d{2}(:\\d{2})?$"
          },
          "end_time": {
            "type": "string",
            "pattern": "^\\d{2}:\\d{2}(:\\d{2})?$"
          },
          "location_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)$"
          },
          "employee_id": {
            "nullable": true,
            "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)$"
          },
          "position_id": {
            "nullable": true,
            "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",
              "published"
            ]
          },
          "label": {
            "nullable": true,
            "type": "string",
            "maxLength": 50
          },
          "open": {
            "type": "boolean"
          },
          "is_substitution": {
            "type": "boolean"
          }
        },
        "additionalProperties": false,
        "id": "ShiftPatch",
        "description": "Only the fields you send change. Send `employee_id: null` to free the slot."
      },
      "Deleted": {
        "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)$"
          },
          "deleted": {
            "type": "boolean",
            "enum": [
              true
            ]
          }
        },
        "required": [
          "id",
          "deleted"
        ],
        "additionalProperties": false,
        "id": "Deleted"
      },
      "EmployeeUpsert": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "hire_date": {
            "nullable": true,
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "personnel_no": {
            "nullable": true,
            "type": "string",
            "maxLength": 64
          },
          "phone": {
            "description": "International format: + and country code, 7 to 15 digits; spaces, brackets and dashes allowed. Empty string or null clears it",
            "nullable": true,
            "type": "string",
            "maxLength": 32
          },
          "birth_date": {
            "nullable": true,
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "position_id": {
            "nullable": true,
            "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)$"
          },
          "primary_location_id": {
            "nullable": true,
            "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)$"
          },
          "location_ids": {
            "maxItems": 200,
            "type": "array",
            "items": {
              "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)$"
            }
          },
          "all_locations": {
            "type": "boolean"
          },
          "in_schedule": {
            "type": "boolean"
          },
          "is_intern": {
            "description": "Create the person as an intern. Only when creating: needs the `internships:write` scope and the Internships module on. On an existing person the same value is accepted and ignored; changing it goes through `markEmployeeIntern` and `hireIntern`",
            "type": "boolean"
          }
        },
        "additionalProperties": false,
        "id": "EmployeeUpsert",
        "description": "Fields you do not send are left as they are. Dismissal, invite codes and pay are not fields: dismissal and invite codes have endpoints of their own, pay is set in the product."
      },
      "EmployeeDismissalResult": {
        "type": "object",
        "properties": {
          "employee": {
            "$ref": "#/components/schemas/Employee"
          },
          "future_shifts": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Shifts after today that were turned into unfilled slots or removed, depending on `future_shifts` in the request. Today's shift is left as it is: the person usually works it out"
          },
          "cancelled_requests": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Live swap and substitution requests of this person that were cancelled"
          }
        },
        "required": [
          "employee",
          "future_shifts",
          "cancelled_requests"
        ],
        "additionalProperties": false,
        "id": "EmployeeDismissalResult"
      },
      "EmployeeDismissal": {
        "type": "object",
        "properties": {
          "future_shifts": {
            "description": "What happens to their shifts after today: `open` (default) leaves them as unfilled slots, `delete` removes them",
            "type": "string",
            "enum": [
              "open",
              "delete"
            ]
          },
          "reason_code": {
            "description": "Why, as a code: own_request, probation, discipline, layoff, other. Counted in the product statistics",
            "type": "string",
            "enum": [
              "own_request",
              "probation",
              "discipline",
              "layoff",
              "other"
            ]
          }
        },
        "additionalProperties": false,
        "id": "EmployeeDismissal"
      },
      "InviteCode": {
        "type": "object",
        "properties": {
          "employee_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)$"
          },
          "code": {
            "type": "string",
            "description": "6 characters. Whoever enters it in the InShift app joins under this card"
          },
          "expires_at": {
            "type": "string",
            "description": "After this moment the app no longer accepts the code; 7 days from issue"
          },
          "join_url": {
            "type": "string",
            "description": "A link that opens the InShift app on the join screen with the code filled in, or the app store when the app is not installed"
          }
        },
        "required": [
          "employee_id",
          "code",
          "expires_at",
          "join_url"
        ],
        "additionalProperties": false,
        "id": "InviteCode"
      },
      "InternHireResult": {
        "type": "object",
        "properties": {
          "employee": {
            "$ref": "#/components/schemas/Employee"
          },
          "at": {
            "type": "string",
            "description": "The moment they are staff from, wall clock in the company time zone, YYYY-MM-DDTHH:MM"
          },
          "scheduled": {
            "type": "boolean",
            "description": "true: the moment is in the future and the hire happens by itself then; false: done now"
          },
          "pay_known": {
            "type": "boolean",
            "description": "Staff pay is set for them from that day. false: their shifts as staff have no price yet, and the people who manage pay get a notice to set it"
          }
        },
        "required": [
          "employee",
          "at",
          "scheduled",
          "pay_known"
        ],
        "additionalProperties": false,
        "id": "InternHireResult"
      },
      "InternHire": {
        "type": "object",
        "properties": {
          "at": {
            "description": "From which moment they are staff, wall clock YYYY-MM-DDTHH:MM in the company time zone. Omit for now. In the past it turns their shifts from then on into staff shifts; in the future (up to 30 days) it is planned and happens by itself",
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T([01]\\d|2[0-3]):[0-5]\\d$"
          }
        },
        "additionalProperties": false,
        "id": "InternHire"
      },
      "Absence": {
        "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)$"
          },
          "employee_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)$"
          },
          "type": {
            "type": "string",
            "enum": [
              "vacation",
              "sick",
              "personal",
              "unpaid"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "rejected",
              "cancelled"
            ],
            "description": "No default filter: when counting attendance ask for status=approved explicitly"
          },
          "start_date": {
            "type": "string",
            "description": "A calendar date, YYYY-MM-DD"
          },
          "end_date": {
            "type": "string",
            "description": "A calendar date, YYYY-MM-DD"
          },
          "days": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Calendar days, both ends inclusive"
          },
          "reviewed_at": {
            "nullable": true,
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          },
          "created_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          }
        },
        "required": [
          "id",
          "employee_id",
          "type",
          "status",
          "start_date",
          "end_date",
          "days",
          "reviewed_at",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "id": "Absence",
        "description": "The free-text note a person wrote when asking for time off is deliberately NOT here. \"Chemotherapy on Tuesday\" is health data, and health data must not leave through a scope called \"absences:read\". The machine-readable reason is `type`; that is what a payroll or attendance integration actually needs."
      },
      "AttendanceSession": {
        "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)$"
          },
          "employee_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)$"
          },
          "location_id": {
            "nullable": true,
            "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)$"
          },
          "shift_id": {
            "nullable": true,
            "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)$"
          },
          "date": {
            "type": "string",
            "description": "A calendar date, YYYY-MM-DD"
          },
          "opened_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          },
          "closed_at": {
            "nullable": true,
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          },
          "break_minutes": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "worked_minutes": {
            "nullable": true,
            "description": "null means the session is still open and has no duration yet. It is not zero",
            "type": "number"
          },
          "hours": {
            "nullable": true,
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Hours as a string with two decimals. A string, so no float rounding is introduced on the way"
          },
          "closed_by": {
            "nullable": true,
            "type": "string",
            "enum": [
              "scan",
              "auto",
              "manual"
            ]
          },
          "created_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          }
        },
        "required": [
          "id",
          "employee_id",
          "location_id",
          "shift_id",
          "date",
          "opened_at",
          "closed_at",
          "break_minutes",
          "worked_minutes",
          "hours",
          "closed_by",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "id": "AttendanceSession"
      },
      "TimesheetEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "shift_<uuid> for a scheduled row, att_<uuid> for clock-ins with no shift behind them"
          },
          "date": {
            "type": "string",
            "description": "A calendar date, YYYY-MM-DD"
          },
          "employee_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)$"
          },
          "employee_external_id": {
            "nullable": true,
            "type": "string"
          },
          "personnel_no": {
            "description": "Only present for keys holding employees:read.pii",
            "nullable": true,
            "type": "string"
          },
          "location_id": {
            "nullable": true,
            "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)$"
          },
          "position_id": {
            "nullable": true,
            "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)$"
          },
          "scheduled": {
            "nullable": true,
            "description": "null means the person worked unplanned: there was no shift that day",
            "type": "object",
            "properties": {
              "start_at": {
                "type": "string",
                "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
              },
              "end_at": {
                "type": "string",
                "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
              },
              "hours": {
                "type": "string",
                "pattern": "^-?\\d+\\.\\d{2}$",
                "description": "Hours as a string with two decimals. A string, so no float rounding is introduced on the way"
              }
            },
            "required": [
              "start_at",
              "end_at",
              "hours"
            ],
            "additionalProperties": false
          },
          "actual": {
            "nullable": true,
            "description": "null means attendance is off or the person never clocked in. THIS IS NOT ZERO HOURS",
            "type": "object",
            "properties": {
              "start_at": {
                "nullable": true,
                "type": "string",
                "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
              },
              "end_at": {
                "nullable": true,
                "type": "string",
                "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
              },
              "break_minutes": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "hours": {
                "nullable": true,
                "type": "string",
                "pattern": "^-?\\d+\\.\\d{2}$",
                "description": "Hours as a string with two decimals. A string, so no float rounding is introduced on the way"
              },
              "sessions": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "open": {
                "type": "boolean",
                "description": "At least one session is still open, so the actual hours are incomplete"
              }
            },
            "required": [
              "start_at",
              "end_at",
              "break_minutes",
              "hours",
              "sessions",
              "open"
            ],
            "additionalProperties": false
          },
          "hours": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Hours as a string with two decimals. A string, so no float rounding is introduced on the way"
          },
          "hours_source": {
            "type": "string",
            "enum": [
              "scheduled",
              "actual"
            ],
            "description": "Where the hours field came from"
          },
          "status": {
            "nullable": true,
            "type": "string",
            "enum": [
              "draft",
              "published",
              "confirmed"
            ]
          },
          "shift_id": {
            "nullable": true,
            "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)$"
          },
          "attendance_session_ids": {
            "type": "array",
            "items": {
              "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": [
          "id",
          "date",
          "employee_id",
          "employee_external_id",
          "location_id",
          "position_id",
          "scheduled",
          "actual",
          "hours",
          "hours_source",
          "status",
          "shift_id",
          "attendance_session_ids"
        ],
        "additionalProperties": false,
        "id": "TimesheetEntry"
      },
      "Event": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "evt_<uuid>, stable across requests"
          },
          "type": {
            "type": "string",
            "description": "entity.action, for example shift.updated"
          },
          "entity": {
            "type": "string"
          },
          "action": {
            "type": "string",
            "enum": [
              "created",
              "updated",
              "deleted"
            ]
          },
          "object_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)$",
            "description": "The id of the changed object. Fetch its current state from the matching resource"
          },
          "occurred_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601: 2026-09-01T06:00:00.000Z"
          }
        },
        "required": [
          "id",
          "type",
          "entity",
          "action",
          "object_id",
          "occurred_at"
        ],
        "additionalProperties": false,
        "id": "Event"
      },
      "PayrollSummary": {
        "type": "object",
        "properties": {
          "employee_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)$"
          },
          "employee_external_id": {
            "nullable": true,
            "type": "string"
          },
          "personnel_no": {
            "description": "Only with employees:read.pii",
            "nullable": true,
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "dismissed"
            ]
          },
          "from": {
            "type": "string",
            "description": "A calendar date, YYYY-MM-DD"
          },
          "to": {
            "type": "string",
            "description": "A calendar date, YYYY-MM-DD"
          },
          "accrued_through": {
            "type": "string",
            "description": "The last day money is accrued for: the end of the window or today, whichever is earlier. Future shifts are never money"
          },
          "pay": {
            "$ref": "#/components/schemas/PayrollPay"
          },
          "shifts": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Worked shifts in the window: published or confirmed, drafts excluded"
          },
          "hours": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Hours as a string with two decimals"
          },
          "night_hours": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Buckets overlap on purpose: a night hour on a holiday is in both"
          },
          "holiday_hours": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Hours as a string with two decimals"
          },
          "overtime_hours": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Hours as a string with two decimals"
          },
          "unpriced_shifts": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Shifts in the window with no known price: no pay terms and no rate on that day. Not zero money: unknown money"
          },
          "accrued": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Everything accrued in the window: base plus supplements, rounded to whole currency units like the product screen"
          },
          "base": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "accrued minus premium: hours times the rate, or the shift prices, or the salary share"
          },
          "premium": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Everything above the base: multipliers and flat supplements together"
          },
          "supplements": {
            "$ref": "#/components/schemas/PayrollSupplements"
          },
          "instead_of_rate_days": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Days when the sales percent outgrew the rate and replaced it, so base and sales_percent are not additive on those days"
          },
          "adjustments": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Bonuses minus deductions dated inside the window"
          },
          "paid": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Cash paid out inside the window by payment date. Closings (\"already paid earlier\") are not cash and are excluded"
          },
          "balance": {
            "nullable": true,
            "description": "null when the organization has not set a payroll start date: then nobody owes anybody anything here",
            "allOf": [
              {
                "$ref": "#/components/schemas/PayrollBalance"
              }
            ]
          }
        },
        "required": [
          "employee_id",
          "employee_external_id",
          "status",
          "from",
          "to",
          "accrued_through",
          "pay",
          "shifts",
          "hours",
          "night_hours",
          "holiday_hours",
          "overtime_hours",
          "unpriced_shifts",
          "accrued",
          "base",
          "premium",
          "supplements",
          "instead_of_rate_days",
          "adjustments",
          "paid",
          "balance"
        ],
        "additionalProperties": false,
        "id": "PayrollSummary"
      },
      "PayrollPay": {
        "type": "object",
        "properties": {
          "kind": {
            "nullable": true,
            "description": "How this person is paid at the end of the window. hourly: amount per hour. per_shift: amount per shift. monthly: amount per month, split by the planned shifts of the month. shift_rate: each shift carries its own price. none: works but is not paid (an owner). sales_only: shifts cost nothing, the person earns the sales percent and other extras only. null: no pay terms yet, the legacy hourly rate applies",
            "type": "string",
            "enum": [
              "hourly",
              "per_shift",
              "monthly",
              "shift_rate",
              "none",
              "sales_only"
            ]
          },
          "amount": {
            "nullable": true,
            "description": "The amount behind `kind`. null when there are no pay terms",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "hourly_rate": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "The hourly rate in force on the last day of the window, from the rate history. 0.00 when the person is paid another way"
          },
          "hourly_rate_from": {
            "nullable": true,
            "description": "The hourly rate on the first day of the window when it changed inside it, otherwise null",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          }
        },
        "required": [
          "kind",
          "amount",
          "hourly_rate",
          "hourly_rate_from"
        ],
        "additionalProperties": false,
        "id": "PayrollPay"
      },
      "PayrollSupplements": {
        "type": "object",
        "properties": {
          "night": {
            "description": "Night premium: the extra percent on the hours inside the night window",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "holiday": {
            "description": "Holiday multiplier: the part above the base rate on holiday hours",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "overtime_daily": {
            "description": "Daily overtime multiplier: the part above the base rate past the daily threshold",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "shift_bonus": {
            "description": "Flat bonus per worked shift",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "overtime_flat": {
            "description": "Flat bonus for overtime: per day or per hour past the threshold",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "away_shift": {
            "description": "Flat bonus for a shift at a location that is not the person own",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "month_day_bonus": {
            "description": "Flat bonus by the ordinal number of the worked day in the month (the 22nd day, the 26th...)",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "sales_percent": {
            "description": "Percent of personal sales or of the location till",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "double_shift": {
            "description": "Flat bonus for a day with two or more shifts",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "mentoring": {
            "description": "Pay for mentoring trainees: per trainee shift or per hour with a trainee, taken from the trainee shift. Can fall on a day without own shifts",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "minimum_top_up": {
            "description": "Top-up to the shift minimum of the sales_percent rule (min_shift_pay): when the base of the day plus sales_percent fall short of the minimum times the shifts of the day, the difference. Other supplements are not counted towards the minimum",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "seniority": {
            "description": "Seniority bonus: a percent of the base pay of the day (hours at the rate, shift price, share of the salary or a sum written into the shift), other supplements excluded. Years count from the hire date; no hire date, no bonus",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          }
        },
        "additionalProperties": false,
        "id": "PayrollSupplements",
        "description": "Money above the base pay, split by the event that produced it. A key is present when a rule of that type applied on at least one day, even if it yielded 0.00. Holiday and daily overtime never stack: the larger multiplier wins the whole day segment, so at most one of them is non-zero per segment. Parts keep cents; the summary `premium` is rounded to whole currency units like the product screen, so the sum of parts can differ from it by less than one unit"
      },
      "PayrollBalance": {
        "type": "object",
        "properties": {
          "since": {
            "type": "string",
            "description": "The day this organization started keeping payroll here. Money before it lived elsewhere and is not counted"
          },
          "earned": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Accrued for shifts from `since` through today, supplements included"
          },
          "adjustments": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Bonuses minus deductions from `since`, no upper bound: a bonus dated tomorrow is already a promise"
          },
          "paid": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Paid out from `since`, no upper bound, every kind including closings"
          },
          "due": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "earned + adjustments - paid. Negative means the person was paid more than they earned"
          },
          "unpriced_shifts": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Shifts in the whole account the product cannot price. While above zero, `due` is incomplete"
          }
        },
        "required": [
          "since",
          "earned",
          "adjustments",
          "paid",
          "due",
          "unpriced_shifts"
        ],
        "additionalProperties": false,
        "id": "PayrollBalance",
        "description": "The running account of one person: what the organization owes them right now. Does not depend on the window"
      },
      "PayrollDay": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "description": "A calendar date, YYYY-MM-DD"
          },
          "employee_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)$"
          },
          "employee_external_id": {
            "nullable": true,
            "type": "string"
          },
          "personnel_no": {
            "description": "Only with employees:read.pii",
            "nullable": true,
            "type": "string"
          },
          "hours": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Hours as a string with two decimals"
          },
          "night_hours": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Hours as a string with two decimals"
          },
          "holiday_hours": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Hours as a string with two decimals"
          },
          "overtime_hours": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Hours as a string with two decimals"
          },
          "pay_kind": {
            "nullable": true,
            "description": "The pay term that priced this day. null: legacy hourly rate",
            "type": "string",
            "enum": [
              "hourly",
              "per_shift",
              "monthly",
              "shift_rate",
              "none",
              "sales_only"
            ]
          },
          "hourly_rate": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "The hourly rate in force on this day"
          },
          "amount": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Accrued for the day, with cents. The sum of days equals the summary `accrued` before its final rounding"
          },
          "base": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Money as a string with two decimals, in the company currency (see /v1/company). A string on purpose: never parse it as a float"
          },
          "premium": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Money as a string with two decimals, in the company currency (see /v1/company). A string on purpose: never parse it as a float"
          },
          "supplements": {
            "$ref": "#/components/schemas/PayrollSupplements"
          },
          "unpriced_shifts": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "instead_of_rate": {
            "type": "boolean",
            "description": "The sales percent replaced the rate on this day: amount is the larger of the two, not their sum"
          },
          "preliminary": {
            "type": "boolean",
            "description": "Sales for this day are still coming from an open till session: the number will change"
          },
          "month_day_number": {
            "nullable": true,
            "description": "The ordinal number of this worked day inside its month, the basis of month_day_bonus. null when no such rule exists",
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "personal_sales": {
            "nullable": true,
            "description": "Personal sales of this person on this day from the connected till. null means unknown, never zero; also null before the sales-percent start date of the till (normally the 1st of the month it was connected): earlier collected days are statistics, not pay",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "location_sales": {
            "nullable": true,
            "description": "The whole till of the locations worked on this day. null means unknown, including a day before the sales-percent start date of any of those tills (normally the 1st of the month the till was connected)",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "sales_threshold": {
            "nullable": true,
            "description": "The sales threshold of the sales_percent rule on this day: the percent was paid only on personal_sales or location_sales above it. null: no threshold, the percent counts from the first unit",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "shifts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PayrollDayShift"
            }
          }
        },
        "required": [
          "date",
          "employee_id",
          "employee_external_id",
          "hours",
          "night_hours",
          "holiday_hours",
          "overtime_hours",
          "pay_kind",
          "hourly_rate",
          "amount",
          "base",
          "premium",
          "supplements",
          "unpriced_shifts",
          "instead_of_rate",
          "preliminary",
          "month_day_number",
          "personal_sales",
          "location_sales",
          "sales_threshold",
          "shifts"
        ],
        "additionalProperties": false,
        "id": "PayrollDay",
        "description": "One person, one day: the same engine as the product screen \"how the month was made up\", day by day"
      },
      "PayrollDayShift": {
        "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)$"
          },
          "start_time": {
            "type": "string",
            "description": "Wall-clock time, HH:MM"
          },
          "end_time": {
            "type": "string"
          },
          "location_id": {
            "nullable": true,
            "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)$"
          },
          "position_id": {
            "nullable": true,
            "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)$"
          },
          "is_substitution": {
            "type": "boolean"
          },
          "price": {
            "nullable": true,
            "description": "The agreed price of this very shift, only when a shift_rate pay term is what paid it. null otherwise",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          }
        },
        "required": [
          "id",
          "start_time",
          "end_time",
          "location_id",
          "position_id",
          "is_substitution",
          "price"
        ],
        "additionalProperties": false,
        "id": "PayrollDayShift"
      },
      "Payment": {
        "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)$"
          },
          "employee_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)$"
          },
          "employee_external_id": {
            "nullable": true,
            "type": "string"
          },
          "amount": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Money as a string with two decimals, in the company currency (see /v1/company). A string on purpose: never parse it as a float"
          },
          "kind": {
            "type": "string",
            "enum": [
              "advance",
              "settlement",
              "other",
              "closing"
            ],
            "description": "advance, settlement and other are cash that left the till. closing is \"already paid earlier, just close the period\": it counts against the balance but is not a cash movement on that day"
          },
          "paid_at": {
            "type": "string",
            "description": "The day the money was handed over"
          },
          "run_id": {
            "nullable": true,
            "description": "The pay run that recorded this payment, when it came from one",
            "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)$"
          },
          "version": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Send it as If-Match to change or remove the payment"
          },
          "created_by_this_key": {
            "type": "boolean",
            "description": "Recorded with the key making this request. A key changes and removes only its own payments"
          },
          "created_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601"
          },
          "updated_at": {
            "nullable": true,
            "type": "string",
            "description": "An instant in UTC, ISO-8601"
          }
        },
        "required": [
          "id",
          "employee_id",
          "employee_external_id",
          "amount",
          "kind",
          "paid_at",
          "run_id",
          "version",
          "created_by_this_key",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "id": "Payment",
        "description": "Deleted payments are never returned: they are not money anymore"
      },
      "PayrollAdjustment": {
        "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)$"
          },
          "employee_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)$"
          },
          "employee_external_id": {
            "nullable": true,
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "bonus",
              "deduction",
              "opening"
            ],
            "description": "`opening` is the opening balance owed to the person on the payroll start day: debt carried in from before the account was kept here, not an accrual of this period"
          },
          "amount": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Always positive; the sign comes from `kind`"
          },
          "signed_amount": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "The same amount with its sign: a deduction is negative"
          },
          "date": {
            "type": "string",
            "description": "The day it was booked on: it decides which window and which side of a pay run it falls into"
          },
          "version": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Send it as If-Match to change or remove the bonus or deduction"
          },
          "created_by_this_key": {
            "type": "boolean",
            "description": "Recorded with the key making this request. A key changes and removes only its own bonuses and deductions"
          },
          "created_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601"
          },
          "updated_at": {
            "nullable": true,
            "type": "string",
            "description": "An instant in UTC, ISO-8601"
          }
        },
        "required": [
          "id",
          "employee_id",
          "employee_external_id",
          "kind",
          "amount",
          "signed_amount",
          "date",
          "version",
          "created_by_this_key",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "id": "PayrollAdjustment",
        "description": "A one-off bonus, deduction or opening balance. Changes what is accrued, not what is paid. The free-text reason is not exposed"
      },
      "PaymentCreate": {
        "type": "object",
        "properties": {
          "employee_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)$"
          },
          "amount": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^\\d{1,8}(\\.\\d{1,2})?$"
              },
              {
                "type": "number"
              }
            ],
            "description": "Positive, up to 99999999, at most two decimals: \"500.00\" (a number is accepted too)"
          },
          "kind": {
            "type": "string",
            "enum": [
              "advance",
              "other"
            ],
            "description": "advance or other. Settlements and closing a period are done in the product"
          },
          "paid_at": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "The day the money was handed over, YYYY-MM-DD. Not before the payroll start day"
          }
        },
        "required": [
          "employee_id",
          "amount",
          "kind",
          "paid_at"
        ],
        "additionalProperties": false,
        "id": "PaymentCreate"
      },
      "PaymentUpdate": {
        "type": "object",
        "properties": {
          "amount": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^\\d{1,8}(\\.\\d{1,2})?$"
              },
              {
                "type": "number"
              }
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "advance",
              "other"
            ]
          },
          "paid_at": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          }
        },
        "additionalProperties": false,
        "id": "PaymentUpdate"
      },
      "PayrollAdjustmentCreate": {
        "type": "object",
        "properties": {
          "employee_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)$"
          },
          "kind": {
            "type": "string",
            "enum": [
              "bonus",
              "deduction"
            ],
            "description": "bonus adds to what is accrued, deduction takes from it"
          },
          "amount": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^\\d{1,8}(\\.\\d{1,2})?$"
              },
              {
                "type": "number"
              }
            ],
            "description": "Positive, up to 99999999, at most two decimals: \"300.00\"; the sign comes from `kind`"
          },
          "date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "The day it is booked on, YYYY-MM-DD. Not before the payroll start day"
          },
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 300,
            "description": "Why, as the person will read it in the app: \"Sales plan for October\". Write-only: never returned"
          }
        },
        "required": [
          "employee_id",
          "kind",
          "amount",
          "date",
          "reason"
        ],
        "additionalProperties": false,
        "id": "PayrollAdjustmentCreate"
      },
      "PayrollAdjustmentUpdate": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "bonus",
              "deduction"
            ]
          },
          "amount": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^\\d{1,8}(\\.\\d{1,2})?$"
              },
              {
                "type": "number"
              }
            ]
          },
          "date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 300,
            "description": "Why, as the person will read it in the app: \"Sales plan for October\". Write-only: never returned"
          }
        },
        "additionalProperties": false,
        "id": "PayrollAdjustmentUpdate"
      },
      "PayrollRun": {
        "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)$"
          },
          "period_from": {
            "type": "string",
            "description": "A calendar date, YYYY-MM-DD"
          },
          "period_to": {
            "type": "string",
            "description": "Inclusive"
          },
          "paid_at": {
            "type": "string",
            "description": "A calendar date, YYYY-MM-DD"
          },
          "kind": {
            "type": "string",
            "enum": [
              "settlement",
              "closing"
            ],
            "description": "settlement: cash left the till. closing: the money had been paid before, the run only closes the period"
          },
          "status": {
            "type": "string",
            "enum": [
              "confirmed",
              "cancelled"
            ]
          },
          "people_count": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "total": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Money as a string with two decimals, in the company currency (see /v1/company). A string on purpose: never parse it as a float"
          },
          "created_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601"
          },
          "cancelled_at": {
            "nullable": true,
            "type": "string",
            "description": "An instant in UTC, ISO-8601"
          },
          "rows": {
            "description": "Only on GET /v1/payroll/runs/{id}",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PayrollRunRow"
            }
          }
        },
        "required": [
          "id",
          "period_from",
          "period_to",
          "paid_at",
          "kind",
          "status",
          "people_count",
          "total",
          "created_at",
          "cancelled_at"
        ],
        "additionalProperties": false,
        "id": "PayrollRun",
        "description": "A pay run: one period closed for everybody at once. Rows are frozen at confirmation and never recomputed"
      },
      "PayrollRunRow": {
        "type": "object",
        "properties": {
          "employee_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)$"
          },
          "employee_external_id": {
            "nullable": true,
            "type": "string"
          },
          "accrued": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Accrued from the payroll start through period_to, frozen at confirmation"
          },
          "paid_before": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Paid before this run, over the whole account"
          },
          "due": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "accrued - paid_before at the moment of confirmation"
          },
          "amount": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "What this run paid. Less than due means a partial payout"
          },
          "settled": {
            "type": "boolean",
            "description": "A settlement line was drawn at period_to: only on a full payout"
          }
        },
        "required": [
          "employee_id",
          "employee_external_id",
          "accrued",
          "paid_before",
          "due",
          "amount",
          "settled"
        ],
        "additionalProperties": false,
        "id": "PayrollRunRow"
      },
      "PayrollRule": {
        "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)$"
          },
          "type": {
            "type": "string",
            "enum": [
              "night",
              "holiday",
              "overtime_daily",
              "shift_bonus",
              "overtime_flat",
              "away_shift",
              "month_day_bonus",
              "sales_percent",
              "double_shift",
              "mentoring",
              "seniority"
            ]
          },
          "scope": {
            "type": "string",
            "enum": [
              "organization",
              "location",
              "position",
              "employee"
            ],
            "description": "Whom it applies to. The narrower always wins the wider: employee over position over location over organization"
          },
          "scope_id": {
            "nullable": true,
            "description": "The location, position or employee. null for the organization",
            "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)$"
          },
          "effective_from": {
            "type": "string",
            "description": "Rules have history: a rule edited today does not rewrite last month"
          },
          "value": {
            "nullable": true,
            "description": "night, sales_percent and seniority: the percent (20 = +20%). holiday and overtime_daily: the multiplier (2 = double). Others: null",
            "type": "number"
          },
          "amount": {
            "nullable": true,
            "description": "Flat types: the sum of money. Others: null",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "amount_unit": {
            "nullable": true,
            "description": "overtime_flat: per day of overtime (day) or per hour past the threshold (hour). mentoring: per trainee shift (shift) or per hour with a trainee (hour)",
            "type": "string",
            "enum": [
              "day",
              "hour",
              "shift"
            ]
          },
          "threshold_hours": {
            "nullable": true,
            "description": "overtime_daily and overtime_flat: hours per day paid as usual",
            "type": "number"
          },
          "window_start": {
            "nullable": true,
            "description": "night only: HH:MM:SS. An end before the start means the window crosses midnight",
            "type": "string"
          },
          "window_end": {
            "nullable": true,
            "type": "string"
          },
          "away_basis": {
            "nullable": true,
            "description": "away_shift only: which locations count as the person own",
            "type": "string",
            "enum": [
              "primary",
              "any"
            ]
          },
          "day_from": {
            "nullable": true,
            "description": "month_day_bonus only: the first ordinal worked day of the month this step applies to",
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "day_to": {
            "nullable": true,
            "description": "null means no upper bound",
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "sales_base": {
            "nullable": true,
            "description": "sales_percent only: personal sales or the whole till",
            "type": "string",
            "enum": [
              "personal",
              "location"
            ]
          },
          "crew_from": {
            "nullable": true,
            "description": "sales_percent only: the step by the number of people on shift",
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "crew_to": {
            "nullable": true,
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "crew_position_ids": {
            "nullable": true,
            "description": "sales_percent steps on the location till only: who counts as people on shift. One list for the whole ladder (every step with the same scope and effective_from). null: everybody on the schedule of the location. Otherwise only people working these positions on that shift (the shift position, or the one on their card when the shift has none); the person being paid always counts",
            "type": "array",
            "items": {
              "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)$"
            }
          },
          "instead_of_rate": {
            "nullable": true,
            "description": "sales_percent only: pay the rate OR the percent, whichever is larger",
            "type": "boolean"
          },
          "sales_threshold": {
            "nullable": true,
            "description": "sales_percent only: the percent counts only on the sales of the day above this sum (the base of the rule: personal sales or the whole till). null: from the first unit",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "min_shift_pay": {
            "nullable": true,
            "description": "sales_percent only: the base of the day plus the percent is never less than this sum per shift; the difference is paid as the minimum_top_up supplement. null: no minimum",
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$"
          },
          "group_id": {
            "nullable": true,
            "description": "Rules saved together for several people at once share it: each person still has an own employee rule, the group only ties them for editing. null: not part of a group",
            "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)$"
          },
          "years_from": {
            "nullable": true,
            "description": "seniority only: the step starts at this many full years since the hire date. The highest step reached on the day pays. Steps with the same scope and effective_from are one ladder",
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "created_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601"
          }
        },
        "required": [
          "id",
          "type",
          "scope",
          "scope_id",
          "effective_from",
          "value",
          "amount",
          "amount_unit",
          "threshold_hours",
          "window_start",
          "window_end",
          "away_basis",
          "day_from",
          "day_to",
          "sales_base",
          "crew_from",
          "crew_to",
          "crew_position_ids",
          "instead_of_rate",
          "sales_threshold",
          "min_shift_pay",
          "group_id",
          "years_from",
          "created_at"
        ],
        "additionalProperties": false,
        "id": "PayrollRule",
        "description": "One supplement rule as configured. The vocabulary of types is fixed by the product; values, levels and dates are the organization own"
      },
      "PayTerm": {
        "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)$"
          },
          "scope": {
            "type": "string",
            "enum": [
              "organization",
              "position",
              "employee"
            ]
          },
          "scope_id": {
            "nullable": true,
            "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)$"
          },
          "kind": {
            "type": "string",
            "enum": [
              "hourly",
              "per_shift",
              "monthly",
              "shift_rate",
              "none",
              "sales_only"
            ]
          },
          "amount": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "description": "Per hour, per shift or per month, according to `kind`. 0.00 for none and sales_only"
          },
          "effective_from": {
            "type": "string",
            "description": "A calendar date, YYYY-MM-DD"
          },
          "norm_shifts": {
            "nullable": true,
            "description": "monthly only: how many shifts a month count as the full salary. null: the planned shifts of the month",
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "created_at": {
            "type": "string",
            "description": "An instant in UTC, ISO-8601"
          }
        },
        "required": [
          "id",
          "scope",
          "scope_id",
          "kind",
          "amount",
          "effective_from",
          "norm_shifts",
          "created_at"
        ],
        "additionalProperties": false,
        "id": "PayTerm",
        "description": "How people are paid, with history. The narrower level wins: employee over position over organization"
      }
    },
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key issued by the organization owner in Settings → Integrations."
      }
    }
  },
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/v1/company": {
      "get": {
        "operationId": "getCompany",
        "summary": "The organization this key belongs to",
        "description": "Read this first. It carries the time zone every wall-clock time in the API is read in, and which modules are on: an empty /v1/attendance means \"the module is off\" as often as it means \"nobody clocked in\", and only this endpoint tells the two apart.\n\nRequires the `company:read` scope.",
        "tags": [
          "company"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Company"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/locations": {
      "get": {
        "operationId": "listLocations",
        "summary": "Locations",
        "description": "Paginated. Take `next_cursor` from the response verbatim and pass it as `cursor`. Never build a cursor yourself and never assume it is a date or an id.\n\nRequires the `locations:read` scope.",
        "tags": [
          "locations"
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "archived"
              ]
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Location"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "nullable": true,
                      "description": "Pass back as ?cursor= to get the next page",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/locations/{id}": {
      "get": {
        "operationId": "getLocation",
        "summary": "One location",
        "description": "Requires the `locations:read` scope.",
        "tags": [
          "locations"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Location id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Location"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/positions": {
      "get": {
        "operationId": "listPositions",
        "summary": "Job positions",
        "description": "Job positions (\"Barista\"), not access roles. Paginated. Take `next_cursor` from the response verbatim and pass it as `cursor`. Never build a cursor yourself and never assume it is a date or an id.\n\nRequires the `positions:read` scope.",
        "tags": [
          "positions"
        ],
        "parameters": [
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Position"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "nullable": true,
                      "description": "Pass back as ?cursor= to get the next page",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/positions/{id}": {
      "get": {
        "operationId": "getPosition",
        "summary": "One job position",
        "description": "Requires the `positions:read` scope.",
        "tags": [
          "positions"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Position id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Position"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/employees": {
      "get": {
        "operationId": "listEmployees",
        "summary": "People",
        "description": "Contact details (phone, email, date of birth, personnel number) are returned only for keys holding `employees:read.pii`; without it the fields are absent from the response entirely. To match people against your own system use `external_id`, which needs no such scope. Paginated. Take `next_cursor` from the response verbatim and pass it as `cursor`. Never build a cursor yourself and never assume it is a date or an id.\n\nRequires the `employees:read` scope.",
        "tags": [
          "employees"
        ],
        "parameters": [
          {
            "name": "location_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "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)$"
                },
                {
                  "type": "array",
                  "items": {
                    "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)$"
                  }
                }
              ]
            }
          },
          {
            "name": "position_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "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)$"
                },
                {
                  "type": "array",
                  "items": {
                    "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)$"
                  }
                }
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "dismissed"
              ]
            }
          },
          {
            "name": "is_intern",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Employee"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "nullable": true,
                      "description": "Pass back as ?cursor= to get the next page",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/employees/{id}": {
      "get": {
        "operationId": "getEmployee",
        "summary": "One person",
        "description": "Requires the `employees:read` scope.",
        "tags": [
          "employees"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Employee id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Employee"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      },
      "delete": {
        "operationId": "deleteEmployee",
        "summary": "Delete a card created by mistake",
        "description": "Only a card without history: no past published shifts, clock-ins, payouts, bonuses or deductions, closed pay runs or approved time off. Otherwise 409 `employee_has_history`, and the answer is to dismiss the person: deleting would erase money and records. Future shifts of the card go with it. No If-Match: a card has no version, and the history rule is what protects it. Retrying a delete that already succeeded answers 404. Only for people with an ordinary access role: not the owner or co-owners, and not anyone whose role lets them manage people, the schedule or money, or open the admin panel (403 `employee_protected`). Those are handled in the product. The key also stops working for this when the person who issued it is no longer an owner or co-owner (403 `key_issuer_inactive`). AI assistants: say exactly who this is about and wait for confirmation.\n\nRequires the `employees:dismiss` scope.",
        "tags": [
          "employees"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Employee id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/employees/by-external-id/{external_id}": {
      "get": {
        "operationId": "getEmployeeByExternalId",
        "summary": "One person by your own id",
        "description": "Answers with the person or 404, the shape a daily sync actually needs.\n\nRequires the `employees:read` scope.",
        "tags": [
          "employees"
        ],
        "parameters": [
          {
            "name": "external_id",
            "in": "path",
            "required": true,
            "description": "The id this person has in your system",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Employee"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      },
      "put": {
        "operationId": "upsertEmployeeByExternalId",
        "summary": "Create or update a person by your own id",
        "description": "The shape a daily HR sync needs: your id in the path, the fields you know in the body. Creates the person when the id is new (201) and updates them when it is not (200). Fields you do not send stay as they are, so an HR system never wipes what it does not know about. Dismissal is not a field here: it cancels future shifts, closes requests and fixes a date payroll depends on, so it has its own endpoint, `dismissEmployee`.\n\nRequires the `employees:write` scope.",
        "tags": [
          "employees"
        ],
        "parameters": [
          {
            "name": "external_id",
            "in": "path",
            "required": true,
            "description": "The id this person has in your system",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmployeeUpsert"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Employee"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/shifts": {
      "get": {
        "operationId": "listShifts",
        "summary": "Scheduled shifts",
        "description": "Every shift carries both readings of its time: `date` + `start_time`/`end_time` are the wall clock a person sees in the schedule, `start_at`/`end_at` are the same moments in UTC. The `from`/`to` range is required and capped at 366 days. This is what keeps every query on an index; longer periods are fetched year by year. Paginated. Take `next_cursor` from the response verbatim and pass it as `cursor`. Never build a cursor yourself and never assume it is a date or an id.\n\nRequires the `shifts:read` scope.",
        "tags": [
          "shifts"
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "location_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "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)$"
                },
                {
                  "type": "array",
                  "items": {
                    "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)$"
                  }
                }
              ]
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "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)$"
                },
                {
                  "type": "array",
                  "items": {
                    "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)$"
                  }
                }
              ]
            }
          },
          {
            "name": "position_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "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)$"
                },
                {
                  "type": "array",
                  "items": {
                    "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)$"
                  }
                }
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "published",
                "confirmed"
              ]
            }
          },
          {
            "name": "open",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Shift"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "nullable": true,
                      "description": "Pass back as ?cursor= to get the next page",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      },
      "post": {
        "operationId": "createShift",
        "summary": "Create a shift",
        "description": "A shift with `employee_id` is somebody's working day; without it, it is an unfilled slot, and with `open: true` the slot is offered to the team. A published shift reaches the person the same second, exactly as if a manager had put it there: that is deliberate, because for them it is simply their schedule. Publishing needs the `shifts:publish` scope ON TOP of `shifts:write`. Without it a key can only leave drafts, which nobody on the team sees until a person publishes them.\n\nRequires the `shifts:write` scope, and `shifts:publish` on top of it to publish.",
        "tags": [
          "shifts"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Required. Any unique string, for example a UUID. Reuse the SAME value when you retry after a network failure: the answer comes back from the first attempt and nothing is created twice. The same key with a different body is rejected.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ShiftCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Shift"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/shifts/{id}": {
      "get": {
        "operationId": "getShift",
        "summary": "One shift",
        "description": "Requires the `shifts:read` scope.",
        "tags": [
          "shifts"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Shift id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Shift"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      },
      "patch": {
        "operationId": "updateShift",
        "summary": "Edit a shift",
        "description": "Only the fields you send change. `employee_id: null` frees the slot. Moving or retiming a published shift notifies the person, because a silent change is how people show up at the old hour. Publishing needs the `shifts:publish` scope ON TOP of `shifts:write`. Without it a key can only leave drafts, which nobody on the team sees until a person publishes them.\n\nRequires the `shifts:write` scope, and `shifts:publish` on top of it to publish.",
        "tags": [
          "shifts"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Shift id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "description": "Required. The `version` you last read, for example `7` (a quoted ETag works too). It fails with 412 when somebody edited the object meanwhile, which is what stops an integration from silently overwriting a manager. Send `*` to edit whatever version is current.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ShiftPatch"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Shift"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "Somebody changed the object since you read it. Read it again and retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "If-Match is missing. Send the version you last read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      },
      "delete": {
        "operationId": "deleteShift",
        "summary": "Remove a shift",
        "description": "The person is told their shift is gone, the same as when a manager removes it. Retrying a delete that already succeeded answers 404: the shift is gone, which is what you asked for.\n\nRequires the `shifts:write` scope.",
        "tags": [
          "shifts"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Shift id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "description": "Required. The `version` you last read, for example `7` (a quoted ETag works too). It fails with 412 when somebody edited the object meanwhile, which is what stops an integration from silently overwriting a manager. Send `*` to edit whatever version is current.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "Somebody changed the object since you read it. Read it again and retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "If-Match is missing. Send the version you last read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/shifts/batch": {
      "post": {
        "operationId": "createShiftsBatch",
        "summary": "Create up to 500 shifts",
        "description": "PARTIAL SUCCESS. 200 means the report is ready, not that everything worked: every row you sent gets a line, with either the shift or the reason it was refused. A month of schedule almost always has a few clashes, and dropping the whole month over them would be worse than useless. Publishing needs the `shifts:publish` scope ON TOP of `shifts:write`. Without it a key can only leave drafts, which nobody on the team sees until a person publishes them.\n\nRequires the `shifts:write` scope, and `shifts:publish` on top of it to publish.",
        "tags": [
          "shifts"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Required. Any unique string, for example a UUID. Reuse the SAME value when you retry after a network failure: the answer comes back from the first attempt and nothing is created twice. The same key with a different body is rejected.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ShiftBatchCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShiftBatchResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/employees/{id}/dismiss": {
      "post": {
        "operationId": "dismissEmployee",
        "summary": "Dismiss a person",
        "description": "The same dismissal as in the product, not a status field: shifts after today become unfilled slots (or are removed with `future_shifts: delete`), live swap and substitution requests are cancelled, a vacation already under way ends today and later ones are cancelled, mentoring of interns stops, invite codes die and the person's app account is disconnected from the organization. Past shifts and money stay. Undo with `restoreEmployee`. Only for people with an ordinary access role: not the owner or co-owners, and not anyone whose role lets them manage people, the schedule or money, or open the admin panel (403 `employee_protected`). Those are handled in the product. The key also stops working for this when the person who issued it is no longer an owner or co-owner (403 `key_issuer_inactive`). AI assistants: say exactly who this is about and wait for confirmation.\n\nRequires the `employees:dismiss` scope.",
        "tags": [
          "employees"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Employee id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Required. Any unique string, for example a UUID. Reuse the SAME value when you retry after a network failure: the answer comes back from the first attempt and nothing is created twice. The same key with a different body is rejected.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmployeeDismissal"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmployeeDismissalResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/employees/{id}/restore": {
      "post": {
        "operationId": "restoreEmployee",
        "summary": "Bring a dismissed person back",
        "description": "The card becomes active again with its history. Shifts that were released at dismissal stay where they are, and the account is not reattached: the person joins again with a new invite code. Only for people with an ordinary access role: not the owner or co-owners, and not anyone whose role lets them manage people, the schedule or money, or open the admin panel (403 `employee_protected`). Those are handled in the product. The key also stops working for this when the person who issued it is no longer an owner or co-owner (403 `key_issuer_inactive`). AI assistants: say exactly who this is about and wait for confirmation.\n\nRequires the `employees:dismiss` scope.",
        "tags": [
          "employees"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Employee id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Required. Any unique string, for example a UUID. Reuse the SAME value when you retry after a network failure: the answer comes back from the first attempt and nothing is created twice. The same key with a different body is rejected.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Employee"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/employees/{id}/invite-code": {
      "post": {
        "operationId": "getEmployeeInviteCode",
        "summary": "Invite code for the app",
        "description": "Returns the live code of this card or issues a new one (7 days). Whoever enters the code in the InShift app joins under this card with its access role, so treat it like a password and hand it to that person only. We send no email: deliver `code` or `join_url` yourself. Not for a dismissed card (409 `employee_dismissed`) or one that already has an account (409 `employee_has_account`). No Idempotency-Key: a retry returns the same live code. Only for people with an ordinary access role: not the owner or co-owners, and not anyone whose role lets them manage people, the schedule or money, or open the admin panel (403 `employee_protected`). Those are handled in the product. The key also stops working for this when the person who issued it is no longer an owner or co-owner (403 `key_issuer_inactive`). AI assistants: say exactly who this is about and wait for confirmation.\n\nRequires the `employees:invite` scope.",
        "tags": [
          "employees"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Employee id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "The created object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InviteCode"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/employees/{id}/invite-code/reissue": {
      "post": {
        "operationId": "reissueEmployeeInviteCode",
        "summary": "Replace the invite code",
        "description": "A new 7-day code instead of the live one, which stops working at once. For a code that went to the wrong place or expired. No Idempotency-Key: if the answer is lost, retrying gives a newer code, and the one you never received is dead. Only for people with an ordinary access role: not the owner or co-owners, and not anyone whose role lets them manage people, the schedule or money, or open the admin panel (403 `employee_protected`). Those are handled in the product. The key also stops working for this when the person who issued it is no longer an owner or co-owner (403 `key_issuer_inactive`). AI assistants: say exactly who this is about and wait for confirmation.\n\nRequires the `employees:invite` scope.",
        "tags": [
          "employees"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Employee id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "The created object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InviteCode"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/employees/{id}/intern": {
      "post": {
        "operationId": "markEmployeeIntern",
        "summary": "Make a person an intern",
        "description": "The same mark as in the product: from the start of today they are an intern, paid the intern rate of their position instead of staff pay, work alongside a mentor and cannot swap, give away or take shifts; their live swap and open-shift requests are closed. To create a new person as an intern, send `is_intern: true` to `upsertEmployeeByExternalId`. Needs the Internships module on (403 `module_off` otherwise). The mentor on a shift and intern pay are set in the product. Only for people with an ordinary access role: not the owner or co-owners, and not anyone whose role lets them manage people, the schedule or money, or open the admin panel (403 `employee_protected`). Those are handled in the product. The key also stops working for this when the person who issued it is no longer an owner or co-owner (403 `key_issuer_inactive`). AI assistants: say exactly who this is about and wait for confirmation.\n\nRequires the `internships:write` scope.",
        "tags": [
          "employees"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Employee id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Required. Any unique string, for example a UUID. Reuse the SAME value when you retry after a network failure: the answer comes back from the first attempt and nothing is created twice. The same key with a different body is rejected.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Employee"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/employees/{id}/intern/cancel": {
      "post": {
        "operationId": "cancelEmployeeInternMark",
        "summary": "Undo an intern mark set by mistake",
        "description": "Removes the mark as if it was never set, only while the intern has no shifts since the internship started (409 `intern_has_shifts` otherwise: hire them instead). Requests closed by the mark do not come back. Needs the Internships module on (403 `module_off` otherwise). The mentor on a shift and intern pay are set in the product. Only for people with an ordinary access role: not the owner or co-owners, and not anyone whose role lets them manage people, the schedule or money, or open the admin panel (403 `employee_protected`). Those are handled in the product. The key also stops working for this when the person who issued it is no longer an owner or co-owner (403 `key_issuer_inactive`). AI assistants: say exactly who this is about and wait for confirmation.\n\nRequires the `internships:write` scope.",
        "tags": [
          "employees"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Employee id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Required. Any unique string, for example a UUID. Reuse the SAME value when you retry after a network failure: the answer comes back from the first attempt and nothing is created twice. The same key with a different body is rejected.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Employee"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/employees/{id}/hire": {
      "post": {
        "operationId": "hireIntern",
        "summary": "Move an intern to the staff",
        "description": "From `at` (wall clock in the company time zone; omit for now) they are staff: their shifts from then on are paid as staff and mentors are taken off them. A past moment rewrites those shifts, but never ones already paid to the intern or their mentor (409 `intern_paid_after`): a key does not rewrite paid money. A future moment, up to 30 days ahead, is planned and happens by itself; `cancelInternHire` drops the plan. Needs the Internships module on (403 `module_off` otherwise). The mentor on a shift and intern pay are set in the product. Only for people with an ordinary access role: not the owner or co-owners, and not anyone whose role lets them manage people, the schedule or money, or open the admin panel (403 `employee_protected`). Those are handled in the product. The key also stops working for this when the person who issued it is no longer an owner or co-owner (403 `key_issuer_inactive`). AI assistants: say exactly who this is about and wait for confirmation.\n\nRequires the `internships:write` scope.",
        "tags": [
          "employees"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Employee id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Required. Any unique string, for example a UUID. Reuse the SAME value when you retry after a network failure: the answer comes back from the first attempt and nothing is created twice. The same key with a different body is rejected.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InternHire"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InternHireResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      },
      "delete": {
        "operationId": "cancelInternHire",
        "summary": "Drop a planned hire",
        "description": "The intern stays an intern and `hire_planned_at` becomes null. Nothing planned: the same answer, the person as they are. No If-Match: the plan has no version, and only the plan goes. Needs the Internships module on (403 `module_off` otherwise). The mentor on a shift and intern pay are set in the product. Only for people with an ordinary access role: not the owner or co-owners, and not anyone whose role lets them manage people, the schedule or money, or open the admin panel (403 `employee_protected`). Those are handled in the product. The key also stops working for this when the person who issued it is no longer an owner or co-owner (403 `key_issuer_inactive`). AI assistants: say exactly who this is about and wait for confirmation.\n\nRequires the `internships:write` scope.",
        "tags": [
          "employees"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Employee id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Employee"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/absences": {
      "get": {
        "operationId": "listAbsences",
        "summary": "Vacation, sick leave, days off",
        "description": "These are requests, so they carry a status. Counting attendance almost always means `status=approved`, and you have to say so: no status filter is applied by default. The range means OVERLAP, not containment: a vacation from Dec 25 to Jan 10 appears in both months. The `from`/`to` range is required and capped at 366 days. This is what keeps every query on an index; longer periods are fetched year by year.\n\nRequires the `absences:read` scope.",
        "tags": [
          "absences"
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "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)$"
                },
                {
                  "type": "array",
                  "items": {
                    "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)$"
                  }
                }
              ]
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "vacation",
                "sick",
                "personal",
                "unpaid"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "approved",
                "rejected",
                "cancelled"
              ]
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Absence"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "nullable": true,
                      "description": "Pass back as ?cursor= to get the next page",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/attendance": {
      "get": {
        "operationId": "listAttendance",
        "summary": "Raw clock-in records",
        "description": "The fact, not the timesheet. An open session has `worked_minutes: null`, not 0: it is still running, and a zero would read as \"did nothing\". The `from`/`to` range is required and capped at 366 days. This is what keeps every query on an index; longer periods are fetched year by year.\n\nRequires the `attendance:read` scope.",
        "tags": [
          "attendance"
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "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)$"
                },
                {
                  "type": "array",
                  "items": {
                    "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)$"
                  }
                }
              ]
            }
          },
          {
            "name": "location_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "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)$"
                },
                {
                  "type": "array",
                  "items": {
                    "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)$"
                  }
                }
              ]
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "closed"
              ]
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AttendanceSession"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "nullable": true,
                      "description": "Pass back as ?cursor= to get the next page",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/timesheets": {
      "get": {
        "operationId": "listTimesheets",
        "summary": "Timesheet: person × day × location",
        "description": "Plan and fact reconciled. `actual: null` means the attendance module is off or the person never clocked in. It does NOT mean zero hours, and reading it as zero is how integrations underpay people. `hours` is the number this organization pays by today (the plan); `hours_source` says where it came from. No money anywhere in v1: no rates, no amounts. The `from`/`to` range is required and capped at 366 days. This is what keeps every query on an index; longer periods are fetched year by year.\n\nRequires the `timesheets:read` scope.",
        "tags": [
          "timesheets"
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "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)$"
                },
                {
                  "type": "array",
                  "items": {
                    "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)$"
                  }
                }
              ]
            }
          },
          {
            "name": "location_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "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)$"
                },
                {
                  "type": "array",
                  "items": {
                    "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)$"
                  }
                }
              ]
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TimesheetEntry"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "nullable": true,
                      "description": "Pass back as ?cursor= to get the next page",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/events": {
      "get": {
        "operationId": "listEvents",
        "summary": "Change feed",
        "description": "What changed since last time, deletions included. A timestamp filter can never report a deletion, because the row is gone. Events carry no body: fetch the current state from the resource. Kept for 90 days. Paginated. Take `next_cursor` from the response verbatim and pass it as `cursor`. Never build a cursor yourself and never assume it is a date or an id.\n\nRequires the `events:read` scope.",
        "tags": [
          "events"
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "minLength": 3,
                  "maxLength": 64
                },
                {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 64
                  }
                }
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Event"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "nullable": true,
                      "description": "Pass back as ?cursor= to get the next page",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/payroll": {
      "get": {
        "operationId": "listPayroll",
        "summary": "Payroll: one row per person for a period",
        "description": "The same engine and the same numbers as the payroll screen in the admin panel, with the money explained: base, premium and a split of every supplement by the event that produced it (night, holiday, overtime, per-shift bonus, away shift, N-th day of the month, sales percent, double shift). Three different numbers live in a row and must not be mixed: `accrued` is for the days of the window through today, `paid` is cash that left the till inside the window by payment date, and `balance` is what the organization owes right now, cumulative from the payroll start date and independent of the window. A person with `unpriced_shifts` above zero worked days the product cannot price yet: read their money as incomplete, not as small. For a key narrowed to locations, accrued and balance include all shifts of the key's people, also at other locations. Every amount is a string with two decimals in the company currency: never parse it as a float. The `from`/`to` range is required and capped at 366 days. This is what keeps every query on an index; longer periods are fetched year by year.\n\nRequires the `payroll:read` scope.",
        "tags": [
          "payroll"
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "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)$"
                },
                {
                  "type": "array",
                  "items": {
                    "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)$"
                  }
                }
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PayrollSummary"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "nullable": true,
                      "description": "Pass back as ?cursor= to get the next page",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/payroll/days": {
      "get": {
        "operationId": "listPayrollDays",
        "summary": "Payroll: one row per person per day",
        "description": "How each person's money was made up, day by day: hours by bucket, the rate that applied, the amount with its base/premium split and every supplement named. Only days with paid shifts (published or confirmed) through today; drafts and future days are the schedule, not money. Sums of days equal the summary `accrued` before its final rounding. `personal_sales` and `location_sales` come from the connected till and are null when unknown, never zero. Sorted by date, then employee. For a key narrowed to locations, the days include all shifts of the key's people, also at other locations. Every amount is a string with two decimals in the company currency: never parse it as a float. The `from`/`to` range is required and capped at 366 days. This is what keeps every query on an index; longer periods are fetched year by year.\n\nRequires the `payroll:read` scope.",
        "tags": [
          "payroll"
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "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)$"
                },
                {
                  "type": "array",
                  "items": {
                    "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)$"
                  }
                }
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PayrollDay"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "nullable": true,
                      "description": "Pass back as ?cursor= to get the next page",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/payments": {
      "get": {
        "operationId": "listPayments",
        "summary": "Payouts to people",
        "description": "Money that was handed over, by payment date. Advances, settlements and other are cash; `closing` is a period closed as \"already paid earlier\" and is not a cash movement, so exclude it when reconciling the till. Deleted payments are never returned. The `from`/`to` range is required and capped at 366 days. This is what keeps every query on an index; longer periods are fetched year by year.\n\nRequires the `payroll:read` scope.",
        "tags": [
          "payments"
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "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)$"
                },
                {
                  "type": "array",
                  "items": {
                    "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)$"
                  }
                }
              ]
            }
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "advance",
                "settlement",
                "other",
                "closing"
              ]
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Payment"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "nullable": true,
                      "description": "Pass back as ?cursor= to get the next page",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      },
      "post": {
        "operationId": "createPayment",
        "summary": "Record an advance or another payout",
        "description": "Money handed over to a person: `advance` or `other`. Settlements and closing a period stay in the product, where the person doing them sees the amounts due. Retrying with the same Idempotency-Key never pays twice. A key records money for the people whose pay it can read, and changes or removes only what it recorded itself (403 `record_not_own` otherwise). It stops working when the person who issued it is no longer an owner or co-owner (403 `key_issuer_inactive`). A day before the payroll start day is refused: it would not count. The owner sees each entry in the product signed with the key name. AI assistants: say the person, the amount and the day, and wait for confirmation. Every amount is a string with two decimals in the company currency: never parse it as a float.\n\nRequires the `payroll:write` scope.",
        "tags": [
          "payments"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Required. Any unique string, for example a UUID. Reuse the SAME value when you retry after a network failure: the answer comes back from the first attempt and nothing is created twice. The same key with a different body is rejected.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/payroll/adjustments": {
      "get": {
        "operationId": "listPayrollAdjustments",
        "summary": "Bonuses and deductions",
        "description": "One-off amounts that change what is accrued, not what is paid. `signed_amount` already carries the sign. The free-text reason is not exposed. The `from`/`to` range is required and capped at 366 days. This is what keeps every query on an index; longer periods are fetched year by year.\n\nRequires the `payroll:read` scope.",
        "tags": [
          "payroll"
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "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)$"
                },
                {
                  "type": "array",
                  "items": {
                    "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)$"
                  }
                }
              ]
            }
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "bonus",
                "deduction",
                "opening"
              ]
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PayrollAdjustment"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "nullable": true,
                      "description": "Pass back as ?cursor= to get the next page",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      },
      "post": {
        "operationId": "createPayrollAdjustment",
        "summary": "Record a bonus or a deduction",
        "description": "Changes what is accrued, not what is paid. `reason` is shown to the person in the app and is never returned. Only the owner changes their own pay: a key issued by a co-owner refuses their own card (403 `own_pay_change`). Retrying with the same Idempotency-Key never books it twice. A key records money for the people whose pay it can read, and changes or removes only what it recorded itself (403 `record_not_own` otherwise). It stops working when the person who issued it is no longer an owner or co-owner (403 `key_issuer_inactive`). A day before the payroll start day is refused: it would not count. The owner sees each entry in the product signed with the key name. AI assistants: say the person, the amount and the day, and wait for confirmation. Every amount is a string with two decimals in the company currency: never parse it as a float.\n\nRequires the `payroll:write` scope.",
        "tags": [
          "payroll"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Required. Any unique string, for example a UUID. Reuse the SAME value when you retry after a network failure: the answer comes back from the first attempt and nothing is created twice. The same key with a different body is rejected.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PayrollAdjustmentCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayrollAdjustment"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/payments/{id}": {
      "patch": {
        "operationId": "updatePayment",
        "summary": "Change a payout this key recorded",
        "description": "Amount, kind or day; the person never changes (remove the payment and record a new one). A key records money for the people whose pay it can read, and changes or removes only what it recorded itself (403 `record_not_own` otherwise). It stops working when the person who issued it is no longer an owner or co-owner (403 `key_issuer_inactive`). A day before the payroll start day is refused: it would not count. The owner sees each entry in the product signed with the key name. AI assistants: say the person, the amount and the day, and wait for confirmation.\n\nRequires the `payroll:write` scope.",
        "tags": [
          "payments"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Payment id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "description": "Required. The `version` you last read, for example `7` (a quoted ETag works too). It fails with 412 when somebody edited the object meanwhile, which is what stops an integration from silently overwriting a manager. Send `*` to edit whatever version is current.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "Somebody changed the object since you read it. Read it again and retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "If-Match is missing. Send the version you last read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      },
      "delete": {
        "operationId": "deletePayment",
        "summary": "Remove a payout this key recorded",
        "description": "It stops counting as paid and stays crossed out in the product journal. Retrying a delete that already succeeded answers 404. A key records money for the people whose pay it can read, and changes or removes only what it recorded itself (403 `record_not_own` otherwise). It stops working when the person who issued it is no longer an owner or co-owner (403 `key_issuer_inactive`). A day before the payroll start day is refused: it would not count. The owner sees each entry in the product signed with the key name. AI assistants: say the person, the amount and the day, and wait for confirmation.\n\nRequires the `payroll:write` scope.",
        "tags": [
          "payments"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Payment id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "description": "Required. The `version` you last read, for example `7` (a quoted ETag works too). It fails with 412 when somebody edited the object meanwhile, which is what stops an integration from silently overwriting a manager. Send `*` to edit whatever version is current.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "Somebody changed the object since you read it. Read it again and retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "If-Match is missing. Send the version you last read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/payroll/adjustments/{id}": {
      "patch": {
        "operationId": "updatePayrollAdjustment",
        "summary": "Change a bonus or deduction this key recorded",
        "description": "Kind, amount, day or reason; the person never changes. A key records money for the people whose pay it can read, and changes or removes only what it recorded itself (403 `record_not_own` otherwise). It stops working when the person who issued it is no longer an owner or co-owner (403 `key_issuer_inactive`). A day before the payroll start day is refused: it would not count. The owner sees each entry in the product signed with the key name. AI assistants: say the person, the amount and the day, and wait for confirmation.\n\nRequires the `payroll:write` scope.",
        "tags": [
          "payroll"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Bonus or deduction id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "description": "Required. The `version` you last read, for example `7` (a quoted ETag works too). It fails with 412 when somebody edited the object meanwhile, which is what stops an integration from silently overwriting a manager. Send `*` to edit whatever version is current.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PayrollAdjustmentUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayrollAdjustment"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "Somebody changed the object since you read it. Read it again and retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "If-Match is missing. Send the version you last read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      },
      "delete": {
        "operationId": "deletePayrollAdjustment",
        "summary": "Remove a bonus or deduction this key recorded",
        "description": "It stops counting and stays crossed out in the product. Retrying answers 404. A key records money for the people whose pay it can read, and changes or removes only what it recorded itself (403 `record_not_own` otherwise). It stops working when the person who issued it is no longer an owner or co-owner (403 `key_issuer_inactive`). A day before the payroll start day is refused: it would not count. The owner sees each entry in the product signed with the key name. AI assistants: say the person, the amount and the day, and wait for confirmation.\n\nRequires the `payroll:write` scope.",
        "tags": [
          "payroll"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Bonus or deduction id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "description": "Required. The `version` you last read, for example `7` (a quoted ETag works too). It fails with 412 when somebody edited the object meanwhile, which is what stops an integration from silently overwriting a manager. Send `*` to edit whatever version is current.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict: the person already works that time, the employee is dismissed, the employee has approved time off that day, or this Idempotency-Key was used with a different body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "Somebody changed the object since you read it. Read it again and retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "428": {
            "description": "If-Match is missing. Send the version you last read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/payroll/runs": {
      "get": {
        "operationId": "listPayrollRuns",
        "summary": "Pay runs: periods closed for everybody at once",
        "description": "Each run freezes what was due and what was paid per person at the moment of confirmation, and is never recomputed. Cancelled runs stay in the list with `status: cancelled`. Newest first. Paginated. Take `next_cursor` from the response verbatim and pass it as `cursor`. Never build a cursor yourself and never assume it is a date or an id.\n\nRequires the `payroll:read` scope.",
        "tags": [
          "payroll"
        ],
        "parameters": [
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PayrollRun"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "nullable": true,
                      "description": "Pass back as ?cursor= to get the next page",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/payroll/runs/{id}": {
      "get": {
        "operationId": "getPayrollRun",
        "summary": "One pay run with its rows",
        "description": "Requires the `payroll:read` scope.",
        "tags": [
          "payroll"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Pay run id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayrollRun"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/payroll/rules": {
      "get": {
        "operationId": "listPayrollRules",
        "summary": "Supplement rules, with history",
        "description": "The configuration behind the `supplements` keys: which events this organization pays for, how much, at which level (organization, location, position, employee) and from which date. The vocabulary of types is fixed by the product; a rule edited today does not rewrite last month. Not paginated: an organization has dozens of rules at most. Personal (employee-level) rules follow the key: only for people within its locations and whose pay its issuer may see.\n\nRequires the `payroll:read` scope.",
        "tags": [
          "payroll"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "A page of results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PayrollRule"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "nullable": true,
                      "description": "Pass back as ?cursor= to get the next page",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/v1/payroll/pay-terms": {
      "get": {
        "operationId": "listPayTerms",
        "summary": "Pay terms, with history",
        "description": "How people are paid: per hour, per shift, per month, by the price of each shift, or not at all (an owner). The narrower level wins: employee over position over organization. Not paginated. Personal (employee-level) terms follow the key: only for people within its locations and whose pay its issuer may see.\n\nRequires the `payroll:read` scope.",
        "tags": [
          "payroll"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "A page of results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PayTerm"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "nullable": true,
                      "description": "Pass back as ?cursor= to get the next page",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Malformed request: unknown parameter or field, bad cursor, range too wide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the required scope, or the organization is suspended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource in THIS organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    }
  }
}
