{
  "openapi": "3.0.3",
  "info": {
    "title": "JUST-IN Public API",
    "version": "1.0.1",
    "description": "Secure, read-only REST access to your JUST-IN attendance records, leave requests, and organizational structure — built for external integrations. Every token is scoped to a single company and to a precise set of abilities. See https://just-in.co.il/api-docs for the full narrative documentation.",
    "contact": {
      "name": "JUST-IN",
      "url": "https://just-in.co.il/api-docs"
    }
  },
  "servers": [
    { "url": "https://just-in.co.il/api/v1", "description": "Production" }
  ],
  "security": [{ "bearerAuth": [] }],
  "tags": [
    { "name": "Attendance" },
    { "name": "Employees" },
    { "name": "Leave" },
    { "name": "Shifts" }
  ],
  "paths": {
    "/attendance": {
      "get": {
        "tags": ["Attendance"],
        "summary": "List attendance records",
        "description": "Requires ability attendance:read. GPS location fields are only included when the token also has attendance:location:read.",
        "parameters": [
          { "name": "date_from", "in": "query", "schema": { "type": "string", "format": "date" } },
          { "name": "date_to", "in": "query", "schema": { "type": "string", "format": "date" } },
          { "name": "employee_id", "in": "query", "schema": { "type": "integer" } },
          { "name": "department_id", "in": "query", "schema": { "type": "integer" } },
          { "name": "branch_id", "in": "query", "schema": { "type": "integer" } },
          { "name": "status", "in": "query", "schema": { "type": "string" } },
          { "name": "per_page", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AttendancePage" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/employees": {
      "get": {
        "tags": ["Employees"],
        "summary": "List employees",
        "description": "Requires ability employees:read. Never returns salary, national ID number, or vacation/sick balances.",
        "parameters": [
          { "name": "department_id", "in": "query", "schema": { "type": "integer" } },
          { "name": "branch_id", "in": "query", "schema": { "type": "integer" } },
          { "name": "status", "in": "query", "schema": { "type": "string" } },
          { "name": "per_page", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EmployeePage" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      },
      "post": {
        "tags": ["Employees"],
        "summary": "Create an employee",
        "description": "Requires ability employees:write (separate from employees:read). role is always 'employee' server-side and cannot be set by the caller. A login password is generated automatically and sent to the employee by SMS — it is never accepted as input or returned in the response.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/EmployeeCreateRequest" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Employee" } } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/employees/{id}": {
      "get": {
        "tags": ["Employees"],
        "summary": "Get a single employee",
        "description": "Requires ability employees:read. An id belonging to another company returns 404, not 403, so as not to reveal its existence.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Employee" } } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "patch": {
        "tags": ["Employees"],
        "summary": "Update an employee",
        "description": "Requires ability employees:write. Only basic profile fields can be changed here (name, email, phone, address, department_id, branch_id). role and status can never be changed through this endpoint — use /activate and /deactivate for status.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/EmployeeUpdateRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Employee" } } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/employees/{id}/deactivate": {
      "post": {
        "tags": ["Employees"],
        "summary": "Freeze (deactivate) an employee",
        "description": "Requires ability employees:write. Sets the employee's status to frozen — they can no longer log in or punch in/out, but their historical records are preserved. Equivalent to the 'הקפאת עובד' action, newly available to company admins (previously super-admin only).",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string" }, "data": { "$ref": "#/components/schemas/Employee" } } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/employees/{id}/activate": {
      "post": {
        "tags": ["Employees"],
        "summary": "Reactivate a frozen employee",
        "description": "Requires ability employees:write. Sets the employee's status back to active.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "message": { "type": "string" }, "data": { "$ref": "#/components/schemas/Employee" } } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/departments": {
      "get": {
        "tags": ["Employees"],
        "summary": "List departments",
        "description": "Requires ability employees:read. Intentionally unpaginated — returns the full list.",
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Department" } } } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/branches": {
      "get": {
        "tags": ["Employees"],
        "summary": "List branches",
        "description": "Requires ability employees:read. Intentionally unpaginated — returns the full list.",
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Branch" } } } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/leave": {
      "get": {
        "tags": ["Leave"],
        "summary": "List leave / absence requests",
        "description": "Requires ability leave:read — a separate ability from employees:read and attendance:read. Never exposes the attached document itself, only whether one exists (has_document).",
        "parameters": [
          { "name": "date_from", "in": "query", "schema": { "type": "string", "format": "date" } },
          { "name": "date_to", "in": "query", "schema": { "type": "string", "format": "date" } },
          { "name": "employee_id", "in": "query", "schema": { "type": "integer" } },
          { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["pending", "approved", "rejected"] } },
          { "name": "type", "in": "query", "schema": { "type": "string" }, "description": "Exact leave type value, e.g. חופש (vacation) or מחלה (sick leave)." },
          { "name": "per_page", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LeaveRequestPage" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/shifts": {
      "get": {
        "tags": ["Shifts"],
        "summary": "List shift wage-rate presets",
        "description": "Requires ability shifts:read. 'Shifts' here means the company's small list of named wage-rate presets (name, start/end time, wage percentage) used for payroll — not an employee scheduling/roster feature, which JUST-IN does not currently have. Intentionally unpaginated — returns the full list, same as /departments and /branches.",
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Shift" } } } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Company-scoped Sanctum token, created via Company Settings → API in the JUST-IN admin interface. Send as: Authorization: Bearer <token>"
      }
    },
    "responses": {
      "Unauthorized": { "description": "Token missing, invalid, or revoked", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Forbidden": { "description": "Token valid but lacks the ability required for this endpoint", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "Record doesn't exist, or belongs to another company", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "ValidationError": { "description": "Invalid query parameter", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "TooManyRequests": { "description": "Rate limit exceeded (60 requests/minute/token)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "message": { "type": "string" },
          "errors": { "type": "object", "additionalProperties": { "type": "array", "items": { "type": "string" } } }
        }
      },
      "PageLinks": {
        "type": "object",
        "properties": {
          "first": { "type": "string", "nullable": true },
          "last": { "type": "string", "nullable": true },
          "prev": { "type": "string", "nullable": true },
          "next": { "type": "string", "nullable": true }
        }
      },
      "PageMeta": {
        "type": "object",
        "properties": {
          "current_page": { "type": "integer" },
          "last_page": { "type": "integer" },
          "per_page": { "type": "integer" },
          "total": { "type": "integer" }
        }
      },
      "GpsPoint": {
        "type": "object",
        "nullable": true,
        "properties": {
          "lat": { "type": "number", "format": "double" },
          "lng": { "type": "number", "format": "double" },
          "accuracy_meters": { "type": "integer", "nullable": true }
        }
      },
      "Attendance": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "employee_id": { "type": "integer" },
          "date": { "type": "string", "format": "date" },
          "check_in": { "type": "string", "format": "date-time", "nullable": true },
          "check_out": { "type": "string", "format": "date-time", "nullable": true },
          "status": { "type": "string" },
          "absence_type": { "type": "string", "nullable": true },
          "is_approved": { "type": "boolean" },
          "location": {
            "type": "object",
            "description": "Only present when the token has attendance:location:read.",
            "properties": {
              "check_in": { "$ref": "#/components/schemas/GpsPoint" },
              "check_out": { "$ref": "#/components/schemas/GpsPoint" }
            }
          }
        }
      },
      "AttendancePage": {
        "type": "object",
        "properties": {
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Attendance" } },
          "links": { "$ref": "#/components/schemas/PageLinks" },
          "meta": { "$ref": "#/components/schemas/PageMeta" }
        }
      },
      "OrgRef": {
        "type": "object",
        "nullable": true,
        "properties": {
          "id": { "type": "integer" },
          "name": { "type": "string" }
        }
      },
      "Employee": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "name": { "type": "string" },
          "email": { "type": "string" },
          "phone": { "type": "string", "nullable": true },
          "role": { "type": "string" },
          "status": { "type": "string" },
          "department": { "$ref": "#/components/schemas/OrgRef" },
          "branch": { "$ref": "#/components/schemas/OrgRef" }
        }
      },
      "EmployeePage": {
        "type": "object",
        "properties": {
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Employee" } },
          "links": { "$ref": "#/components/schemas/PageLinks" },
          "meta": { "$ref": "#/components/schemas/PageMeta" }
        }
      },
      "EmployeeCreateRequest": {
        "type": "object",
        "required": ["name", "id_number", "email", "phone", "salary_type"],
        "properties": {
          "name": { "type": "string", "maxLength": 255 },
          "id_number": { "type": "string", "description": "Israeli national ID (8-9 digits), validated against the Ministry of Interior checksum algorithm and must be unique.", "minLength": 8, "maxLength": 9 },
          "email": { "type": "string", "format": "email", "maxLength": 255 },
          "phone": { "type": "string", "minLength": 9, "maxLength": 15 },
          "department_id": { "type": "integer", "nullable": true, "description": "Must belong to your own company." },
          "branch_id": { "type": "integer", "nullable": true, "description": "Must belong to your own company." },
          "salary_type": { "type": "string", "enum": ["hourly", "global"] },
          "hourly_rate": { "type": "number", "nullable": true, "description": "Required when salary_type is hourly." },
          "global_salary": { "type": "number", "nullable": true, "description": "Required when salary_type is global." }
        }
      },
      "EmployeeUpdateRequest": {
        "type": "object",
        "description": "All fields optional — only send what you want to change. role, status, id_number and salary fields cannot be changed here.",
        "properties": {
          "name": { "type": "string", "maxLength": 255 },
          "email": { "type": "string", "format": "email", "maxLength": 255 },
          "phone": { "type": "string", "nullable": true, "maxLength": 20 },
          "address": { "type": "string", "nullable": true, "maxLength": 255 },
          "department_id": { "type": "integer", "nullable": true },
          "branch_id": { "type": "integer", "nullable": true }
        }
      },
      "Department": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "name": { "type": "string" }
        }
      },
      "Branch": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "name": { "type": "string" },
          "address": { "type": "string", "nullable": true }
        }
      },
      "LeaveRequest": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "employee_id": { "type": "integer" },
          "date": { "type": "string", "format": "date" },
          "type": { "type": "string", "description": "Exact leave type value used system-wide, e.g. חופש (vacation), מחלה (sick leave), מילואים (reserve duty)." },
          "status": { "type": "string", "enum": ["pending", "approved", "rejected"] },
          "manager_note": { "type": "string", "nullable": true },
          "has_document": { "type": "boolean" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "LeaveRequestPage": {
        "type": "object",
        "properties": {
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/LeaveRequest" } },
          "links": { "$ref": "#/components/schemas/PageLinks" },
          "meta": { "$ref": "#/components/schemas/PageMeta" }
        }
      },
      "Shift": {
        "type": "object",
        "description": "A wage-rate preset (App\\Models\\Shift) — not a scheduled shift instance.",
        "properties": {
          "id": { "type": "integer" },
          "name": { "type": "string" },
          "start_time": { "type": "string", "nullable": true, "example": "08:00" },
          "end_time": { "type": "string", "nullable": true, "example": "16:00" },
          "wage_percentage": { "type": "integer", "example": 100 }
        }
      }
    }
  }
}
