{ "openapi": "3.1.0", "info": { "title": "Nexus Ops API", "version": "0.1.1", "description": "Absensi Divisi Operation — OpenAPI di-generate otomatis dari Zod schemas (@hono/zod-openapi)." }, "servers": [ { "url": "http://localhost:3000", "description": "Local" }, { "url": "https://dev-nexus-ops-api.dev-akmal69.workers.dev", "description": "Development" }, { "url": "https://nexus-ops-api.dev-akmal69.workers.dev", "description": "Production" } ], "components": { "securitySchemes": { "Bearer": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" } }, "schemas": { "HealthResponse": { "type": "object", "properties": { "status": { "type": "string", "enum": [ "ok" ] }, "service": { "type": "string" }, "env": { "type": "string" }, "time": { "type": "string", "format": "date-time" } }, "required": [ "status", "service", "env", "time" ] }, "SupervisorRef": { "type": [ "object", "null" ], "properties": { "id": { "type": "string", "format": "uuid" }, "fullName": { "type": "string", "example": "Andi Pratama" } }, "required": [ "id", "fullName" ] }, "SiteRef": { "type": [ "object", "null" ], "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string", "example": "Site A" } }, "required": [ "id", "name" ] }, "PublicUser": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "email": { "type": "string", "format": "email" }, "fullName": { "type": "string" }, "role": { "type": "string", "enum": [ "employee", "supervisor", "hrd" ] }, "isActive": { "type": "boolean" }, "nip": { "type": [ "string", "null" ], "example": "104892" }, "supervisor": { "$ref": "#/components/schemas/SupervisorRef" }, "defaultSite": { "$ref": "#/components/schemas/SiteRef" } }, "required": [ "id", "email", "fullName", "role", "isActive", "nip", "supervisor", "defaultSite" ] }, "LoginResponse": { "type": "object", "properties": { "accessToken": { "type": "string" }, "tokenType": { "type": "string", "enum": [ "Bearer" ] }, "expiresIn": { "type": "string" }, "user": { "$ref": "#/components/schemas/PublicUser" } }, "required": [ "accessToken", "tokenType", "expiresIn", "user" ] }, "ErrorResponse": { "type": "object", "properties": { "error": { "type": "object", "properties": { "code": { "type": "string", "example": "UNAUTHORIZED" }, "message": { "type": "string", "example": "Invalid email or password" } }, "required": [ "code", "message" ] } }, "required": [ "error" ] }, "LoginRequest": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "example": "budi@operation.local" }, "password": { "type": "string", "minLength": 8, "example": "password123" } }, "required": [ "email", "password" ] }, "MeResponse": { "type": "object", "properties": { "user": { "$ref": "#/components/schemas/PublicUser" } }, "required": [ "user" ] }, "ClockStatus": { "type": "string", "enum": [ "not_clocked_in", "clocked_in", "clocked_out" ] }, "MeTodayShiftBrief": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string", "example": "Shift Pagi" }, "startTime": { "type": "string", "description": "Local time HH:mm", "example": "07:30" }, "endTime": { "type": "string", "description": "Local time HH:mm", "example": "16:30" } }, "required": [ "id", "name", "startTime", "endTime" ] }, "MeTodaySiteBrief": { "allOf": [ { "$ref": "#/components/schemas/SiteRef" }, { "type": "object", "properties": { "geofenceActive": { "type": "boolean", "example": true } }, "required": [ "geofenceActive" ] } ] }, "PendingRequestType": { "type": "string", "enum": [ "leave", "overtime" ] }, "PendingRequestStatus": { "type": "string", "enum": [ "pending", "approved", "rejected" ] }, "PendingRequestSummary": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "type": { "$ref": "#/components/schemas/PendingRequestType" }, "title": { "type": "string", "example": "Izin sakit" }, "summary": { "type": "string", "example": "22–23 Sep" }, "status": { "$ref": "#/components/schemas/PendingRequestStatus" } }, "required": [ "id", "type", "title", "summary", "status" ] }, "MeTodayResponse": { "type": "object", "properties": { "date": { "type": "string", "format": "date", "example": "2026-09-22" }, "clockStatus": { "$ref": "#/components/schemas/ClockStatus" }, "clockInAt": { "type": [ "string", "null" ], "format": "date-time" }, "clockOutAt": { "type": [ "string", "null" ], "format": "date-time" }, "targetClockIn": { "type": "string", "example": "07:30" }, "shift": { "$ref": "#/components/schemas/MeTodayShiftBrief" }, "site": { "$ref": "#/components/schemas/MeTodaySiteBrief" }, "pendingRequests": { "type": "array", "items": { "$ref": "#/components/schemas/PendingRequestSummary" } } }, "required": [ "date", "clockStatus", "clockInAt", "clockOutAt", "targetClockIn", "shift", "site", "pendingRequests" ] }, "MeShiftDetail": { "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string", "example": "Shift Pagi" }, "daysLabel": { "type": "string", "example": "Senin–Jumat" }, "startTime": { "type": "string", "example": "07:30" }, "endTime": { "type": "string", "example": "16:30" } }, "required": [ "id", "name", "daysLabel", "startTime", "endTime" ] }, "MeShiftSite": { "allOf": [ { "$ref": "#/components/schemas/SiteRef" }, { "type": "object", "properties": { "detailName": { "type": "string", "example": "Site A — Gudang" }, "radiusMeters": { "type": "integer", "exclusiveMinimum": 0, "example": 150 } }, "required": [ "detailName", "radiusMeters" ] } ] }, "MeShiftResponse": { "type": "object", "properties": { "shift": { "$ref": "#/components/schemas/MeShiftDetail" }, "defaultSite": { "$ref": "#/components/schemas/MeShiftSite" }, "supervisor": { "$ref": "#/components/schemas/SupervisorRef" } }, "required": [ "shift", "defaultSite", "supervisor" ] }, "DashboardMetrics": { "type": "object", "properties": { "present": { "type": "integer", "minimum": 0, "description": "Hadir", "example": 42 }, "late": { "type": "integer", "minimum": 0, "description": "Terlambat", "example": 3 }, "notYet": { "type": "integer", "minimum": 0, "description": "Belum absen", "example": 5 }, "onLeave": { "type": "integer", "minimum": 0, "description": "Izin", "example": 2 } }, "required": [ "present", "late", "notYet", "onLeave" ] }, "LiveAttendanceStatus": { "type": "string", "enum": [ "present", "late", "not_yet", "on_leave" ] }, "DashboardActivityItem": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "fullName": { "type": "string", "example": "Budi Santoso" }, "status": { "$ref": "#/components/schemas/LiveAttendanceStatus" }, "detail": { "type": "string", "example": "Clock-in 07:28 · Site A" }, "occurredAt": { "type": [ "string", "null" ], "format": "date-time" } }, "required": [ "userId", "fullName", "status", "detail", "occurredAt" ] }, "DashboardSummaryResponse": { "type": "object", "properties": { "date": { "type": "string", "format": "date", "example": "2026-09-22" }, "siteId": { "type": [ "string", "null" ], "format": "uuid" }, "shiftId": { "type": [ "string", "null" ], "format": "uuid" }, "shiftLabel": { "type": [ "string", "null" ], "example": "Shift pagi" }, "sitesLabel": { "type": [ "string", "null" ], "example": "Site A & B" }, "metrics": { "$ref": "#/components/schemas/DashboardMetrics" }, "recentActivity": { "type": "array", "items": { "$ref": "#/components/schemas/DashboardActivityItem" } } }, "required": [ "date", "siteId", "shiftId", "shiftLabel", "sitesLabel", "metrics", "recentActivity" ] }, "LiveNotYetItem": { "type": "object", "properties": { "userId": { "type": "string", "format": "uuid" }, "fullName": { "type": "string", "example": "Citra Lestari" }, "siteName": { "type": "string", "example": "Site A" }, "targetClockIn": { "type": "string", "example": "07:30" } }, "required": [ "userId", "fullName", "siteName", "targetClockIn" ] }, "DashboardLiveResponse": { "type": "object", "properties": { "refreshedAt": { "type": "string", "format": "date-time" }, "date": { "type": "string", "format": "date", "example": "2026-09-22" }, "siteId": { "type": [ "string", "null" ], "format": "uuid" }, "shiftId": { "type": [ "string", "null" ], "format": "uuid" }, "shift": { "type": [ "object", "null" ], "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string", "example": "Shift Pagi" } }, "required": [ "id", "name" ] }, "notYetCount": { "type": "integer", "minimum": 0, "example": 5 }, "notYet": { "type": "array", "items": { "$ref": "#/components/schemas/LiveNotYetItem" } } }, "required": [ "refreshedAt", "date", "siteId", "shiftId", "shift", "notYetCount", "notYet" ] } }, "parameters": {} }, "paths": { "/health": { "get": { "tags": [ "System" ], "summary": "Health check", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HealthResponse" } } } } } } }, "/v1/auth/login": { "post": { "tags": [ "Auth" ], "summary": "Login karyawan / supervisor / HRD", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LoginRequest" } } } }, "responses": { "200": { "description": "Login sukses", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LoginResponse" } } } }, "401": { "description": "Kredensial salah", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/auth/me": { "get": { "tags": [ "Auth" ], "summary": "Profil user terautentikasi", "description": "Includes NIP, supervisor, and default site refs for M-P01 when employee profile exists.", "security": [ { "Bearer": [] } ], "responses": { "200": { "description": "Profil user", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MeResponse" } } } }, "401": { "description": "Tidak terautentikasi", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/me/today": { "get": { "tags": [ "Me" ], "summary": "Status absensi & ringkas pengajuan hari ini (M-H01)", "description": "AuthZ: any authenticated role (`employee` | `supervisor` | `hrd`). Single payload for home — clock status, target, site/geofence flag, pending leave/OT. Work date uses org timezone (Asia/Jakarta).", "security": [ { "Bearer": [] } ], "responses": { "200": { "description": "Konteks hari ini untuk beranda", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MeTodayResponse" } } } }, "401": { "description": "Tidak terautentikasi", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "Profil karyawan / shift / site belum lengkap", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/me/shift": { "get": { "tags": [ "Me" ], "summary": "Detail shift & lokasi default (M-H02)", "description": "AuthZ: any authenticated role (`employee` | `supervisor` | `hrd`). Jam kerja, lokasi default + radius geofence, supervisor (nullable).", "security": [ { "Bearer": [] } ], "responses": { "200": { "description": "Detail shift karyawan", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MeShiftResponse" } } } }, "401": { "description": "Tidak terautentikasi", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "Profil karyawan / shift / site belum lengkap", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/dashboard/summary": { "get": { "tags": [ "Dashboard" ], "summary": "Ringkasan operasional harian (W-D01)", "description": "AuthZ: `supervisor` | `hrd`. Metrics: Hadir / Terlambat / Belum absen / Izin + recent activity. Optional filters: siteId, shiftId. Supervisor scope = reportees only.", "security": [ { "Bearer": [] } ], "parameters": [ { "schema": { "type": "string", "format": "uuid", "example": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" }, "required": false, "name": "siteId", "in": "query" }, { "schema": { "type": "string", "format": "uuid", "example": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" }, "required": false, "name": "shiftId", "in": "query" } ], "responses": { "200": { "description": "Ringkasan dashboard", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DashboardSummaryResponse" } } } }, "401": { "description": "Tidak terautentikasi", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "403": { "description": "Role tidak diizinkan", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "Profil viewer tidak ditemukan", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/v1/dashboard/live": { "get": { "tags": [ "Dashboard" ], "summary": "Kehadiran live — belum absen (W-D02)", "description": "AuthZ: `supervisor` | `hrd`. Lists employees who have not clocked in for the selected shift/site. Reuse attendance projections — not a separate live table. Supervisor scope = reportees only.", "security": [ { "Bearer": [] } ], "parameters": [ { "schema": { "type": "string", "format": "uuid", "example": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" }, "required": false, "name": "siteId", "in": "query" }, { "schema": { "type": "string", "format": "uuid", "example": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" }, "required": false, "name": "shiftId", "in": "query" } ], "responses": { "200": { "description": "Daftar belum absen", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DashboardLiveResponse" } } } }, "401": { "description": "Tidak terautentikasi", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "403": { "description": "Role tidak diizinkan", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "Profil viewer tidak ditemukan", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } } }, "webhooks": {} }