# Schedules API 레퍼런스 > **소유**: 코어 · **생성**: `php artisan api:docgen` (실측 기반). @generated 블록은 재생성 시 갱신되며, 사람이 작성한 설명은 보존됩니다. --- ## TL;DR (5초 요약) ```text 1. 이 문서는 실제 API 호출로 실측한 Schedules 엔드포인트 레퍼런스입니다 2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) 3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` --- ### GET /api/admin/schedules - **라우트명**: `api.admin.schedules.index` - **컨트롤러**: `App\Http\Controllers\Api\Admin\ScheduleController@index` - **인증/권한**: `auth:sanctum` + `permission:core.schedules.read` **요청 파라미터** | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | page | query | integer | 아니오 | min 1 | 조회할 페이지 번호 (1부터 시작) | | per_page | query | integer | 아니오 | min 1, max 100 | 페이지당 항목 수 | | filters | query | array | 아니오 | max 10 | 추가 필터 조건 맵 (필드별 조건) | | type | query | string | 아니오 | `artisan`, `shell`, `url` | 유형 필터 (해당 유형의 항목만 조회) | | frequency | query | string | 아니오 | `everyMinute`, `hourly`, `daily`, `weekly`, `monthly`, `custom` | 실행 주기 | | status | query | string | 아니오 | `active`, `inactive` | 상태 필터 (해당 상태의 항목만 조회) | | last_result | query | string | 아니오 | `success`, `failed`, `running`, `never` | 마지막 실행 결과 필터: success(성공), failed(실패), running(실행중), never(미실행) 중 해당 결과인 스케줄만 조회 | | without_overlapping | query | string | 아니오 | `0`, `1` | 중복 실행 방지 여부 | | run_in_maintenance | query | string | 아니오 | `0`, `1` | 점검 모드 중 실행 여부 | | extension_type | query | string | 아니오 | `core`, `module`, `plugin` | 확장 유형 (core/module/plugin/template) | | extension_identifier | query | string | 아니오 | max 255 | 확장 식별자 | | created_from | query | date | 아니오 | — | 생성일 범위의 시작일 (이 날짜 이후 생성된 스케줄만 조회) | | created_to | query | date | 아니오 | — | 생성일 범위의 종료일 (이 날짜 이전 생성된 스케줄만 조회, created_from 이후여야 함) | | sort_by | query | string | 아니오 | `created_at`, `name`, `next_run_at`, `last_run_at`, `is_active`, `last_result` | 정렬 기준 필드명 | | sort_order | query | string | 아니오 | `asc`, `desc` | 정렬 방향 (asc 오름차순 / desc 내림차순) | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.schedule.list_validation_rules`). **요청 예시** ```http GET /api/admin/schedules?page=1&per_page=1&filters=%EC%98%88%EC%8B%9C%EA%B0%92&type=artisan&frequency=everyMinute&status=active&last_result=success&without_overlapping=0&run_in_maintenance=0&extension_type=core&extension_identifier=example-key&created_from=2026-01-01&created_to=2026-01-01&sort_by=created_at&sort_order=asc HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} ``` **응답 필드** (`data` 내부) _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | id | integer | `1` | 기본 키 (내부 식별자) | | name | string | `API 문서 샘플 스케줄` | 작업명 | | type | string | `artisan` | 작업 유형: artisan(Artisan 커맨드), shell(쉘 명령), url(URL 호출) | | type_label | string | `Artisan 커맨드` | `type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | command | string | `cache:clear` | 명령어 또는 URL | | expression | string | `0 3 * * *` | Cron 표현식 | | frequency | string | `daily` | 실행 주기: everyMinute(매분), hourly(매시간), daily(매일), weekly(매주), monthly(매월), custom(사용자 정의) | | frequency_label | string | `매일` | `frequency` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | without_overlapping | boolean | `true` | 중복 실행 방지 여부: 0(허용), 1(방지) | | run_in_maintenance | boolean | `false` | 점검 모드 실행 여부: 0(비실행), 1(실행) | | is_active | boolean | `true` | active 여부 | | last_result | string | `success` | 마지막 실행 결과: success(성공), failed(실패), running(실행중), never(미실행) | | last_result_label | string | `성공` | `last_result` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | last_run_at | string | `2026-07-30T22:55:00+09:00` | last run 일시 | | last_duration | null | `null` | 마지막 실행의 소요 시간을 사람이 읽는 문자열로 포맷한 값 (예: "45초", "2분 3초" — 마지막 실행 이력의 duration 파생, 실행 이력이 없으면 null) | | next_run_at | string | `2026-08-01T12:00:00+09:00` | next run 일시 | | creator | object | `{"uuid":"a2640dae-e87c-4a28-b4f1-481fd961dc02","name":"AP…` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | | created_at | string | `2026-07-31` | 생성 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true,"c…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** ```http HTTP/1.1 200 ``` ```json { "success": true, "message": "스케줄 목록을 조회했습니다.", "data": { "data": [ { "id": 1, "name": "API 문서 샘플 스케줄", "type": "artisan", "type_label": "Artisan 커맨드", "command": "cache:clear", "expression": "0 3 * * *", "frequency": "daily", "frequency_label": "매일", "without_overlapping": true, "run_in_maintenance": false, "is_active": true, "last_result": "success", "last_result_label": "성공", "last_run_at": "2026-07-30T22:55:00+09:00", "last_duration": null, "next_run_at": "2026-08-01T12:00:00+09:00", "creator": { "uuid": "a2640dae-e87c-4a28-b4f1-481fd961dc02", "name": "API 문서 샘플 사용자" }, "created_at": "2026-07-31", "abilities": { "can_create": true, "can_update": true, "can_delete": true, "can_run": true } } ], "statistics": { "total": 1, "active": 1, "inactive": 0, "success": 1, "failed": 0, "running": 0, "never_run": 0 }, "pagination": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1, "from": 1, "to": 1, "has_more_pages": false } } } ``` **에러 응답** | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`core.schedules.read`)이 없는 경우 | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | **설명** 예약 작업(스케줄) 목록을 페이지네이션과 함께 조회하며, 응답에는 상태별 통계도 포함됩니다. `core.schedules.read` 권한이 필요합니다. `type`/`frequency`/`status`/`last_result`/`extension_type` 등으로 필터링하고 `sort_by`/`sort_order` 로 정렬할 수 있습니다. 관리자 스케줄 관리 화면의 목록·필터·요약 카드 표시에 사용합니다. ### POST /api/admin/schedules - **라우트명**: `api.admin.schedules.store` - **컨트롤러**: `App\Http\Controllers\Api\Admin\ScheduleController@store` - **인증/권한**: `auth:sanctum` + `permission:core.schedules.create` **요청 파라미터** | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | name | body | string | 예 | max 255 | 대상의 이름/명칭 | | description | body | string | 아니오 | max 1000 | 설명 | | type | body | string | 예 | `artisan`, `shell`, `url` | 작업 유형: artisan(Artisan 커맨드 실행), shell(쉘 명령 실행), url(URL 호출) | | command | body | string | 예 | max 2000 | 실행할 명령 (`type` 에 따라 Artisan 커맨드 / 쉘 명령 / 호출할 URL). `type=url` 이면 내부 네트워크 주소(사설 IP·루프백·`localhost`·`*.internal` 등)는 거부되어 422 로 응답합니다 — 서버가 내부망으로 요청을 보내는 것을 막기 위함(SSRF). 사내 엔드포인트를 호출해야 하면 환경설정의 `security.allow_internal_outbound_urls` 를 켜세요. 이 설정을 켜도 userinfo(`https://a@b/`) 위장과 http/https 이외 scheme 은 계속 거부됩니다.
`type=shell` 이면 기본적으로 모든 명령이 거부되어 422 로 응답합니다 — 스케줄 권한만 위임받은 계정이 서버에서 임의 OS 명령을 실행하는 것을 막기 위함입니다. 실행하려면 서버의 `.env` 에서 `SCHEDULE_SHELL_ENABLED=true` 로 게이트를 열고 `SCHEDULE_SHELL_ALLOWED_BINARIES` 에 허용할 실행 파일명을 등록해야 합니다(관리자 화면에서는 넓힐 수 없는 코드 소유 정책). 허용된 실행 파일이라도 셸 메타문자(`|`, `;`, `$`, 백틱, 리다이렉션, 따옴표 등)가 포함되면 거부되며, 실행 시 셸을 경유하지 않습니다. 복합 명령이 필요하면 래퍼 스크립트를 만들어 화이트리스트에 등록하세요.
`type=artisan` 이면 서버가 허용한 유지보수 명령만 등록할 수 있고 그 밖의 명령은 거부되어 422 로 응답합니다 — 캐시·컴파일 산출물 정리(`cache:clear` 등), 큐 처리(`queue:work`·`queue:restart` 등), 만료 데이터 정리, SEO 관련 명령이 여기 해당합니다. `tinker` 처럼 임의 코드를 실행하거나 `migrate`·`db:wipe`·`make:*` 처럼 스키마를 바꾸거나 코드를 생성하는 명령은 허용 목록에 오르지 않습니다(관리자 화면에서는 넓힐 수 없는 코드 소유 정책). 설치된 모듈·플러그인이 제공하는 명령은 자동으로 허용됩니다.
허용된 명령이라도 그 명령이 선언하지 않은 옵션, 위치 인자, 단축 옵션(`-v`)이 붙으면 거부됩니다. 형식은 `명령명 --옵션[=값]` 만 허용하며 따옴표·역슬래시가 포함된 입력은 거부됩니다 — 저장할 때 해석한 명령과 실제로 실행되는 명령이 달라지지 않도록 하기 위함입니다. 거부 사유(허용 목록 밖 / 형식 오류 / 허용되지 않은 옵션 / 추가 인자 불가)는 응답 메시지로 구분되어 안내됩니다 | | expression | body | string | 예 | max 100 | 실행 시각을 정의하는 Cron 표현식 (예: `0 3 * * *`, 다음 실행 시각 next_run_at 계산의 기준) | | frequency | body | string | 예 | `everyMinute`, `hourly`, `daily`, `weekly`, `monthly`, `custom` | 실행 주기 | | without_overlapping | body | boolean | 아니오 | — | 중복 실행 방지 여부 | | run_in_maintenance | body | boolean | 아니오 | — | 점검 모드 중 실행 여부 | | timeout | body | integer | 아니오 | min 1, max 86400 | 타임아웃 (초) | | is_active | body | boolean | 아니오 | — | 활성 여부 (true 활성 / false 비활성) | | extension_type | body | string | 아니오 | `core`, `module`, `plugin` | 확장 유형 (core/module/plugin/template) | | extension_identifier | body | string | 아니오 | max 255 | 확장 식별자 | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.schedule.create_validation_rules`). **요청 예시** ```http POST /api/admin/schedules HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} Content-Type: application/json { "name": "예시 이름", "description": "예시 내용입니다.", "type": "artisan", "command": "예시값", "expression": "예시값", "frequency": "everyMinute", "without_overlapping": true, "run_in_maintenance": true, "timeout": 1, "is_active": true, "extension_type": "core", "extension_identifier": "example-key" } ``` **응답 필드** (`data` 내부) _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | id | integer | `12` | 기본 키 (내부 식별자) | | name | string | `실측 예시값` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | | description | string | `실측 예시값` | 설명 (다국어 필드는 로케일별 값 객체) | | type | string | `artisan` | 작업 유형: artisan(Artisan 커맨드), shell(쉘 명령), url(URL 호출) | | type_label | string | `Artisan 커맨드` | `type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | command | string | `실측 예시값` | 명령어 또는 URL | | expression | string | `실측 예시값` | Cron 표현식 | | frequency | string | `everyMinute` | 실행 주기: everyMinute(매분), hourly(매시간), daily(매일), weekly(매주), monthly(매월), custom(사용자 정의) | | frequency_label | string | `매분` | `frequency` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | without_overlapping | boolean | `true` | 중복 실행 방지 여부: 0(허용), 1(방지) | | run_in_maintenance | boolean | `true` | 점검 모드 실행 여부: 0(비실행), 1(실행) | | timeout | integer | `1` | 실행 제한 시간 (초) | | is_active | boolean | `true` | active 여부 | | last_result | string | `never` | 마지막 실행 결과: success(성공), failed(실패), running(실행중), never(미실행) | | last_result_label | string | `미실행` | `last_result` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | last_duration | null | `null` | 마지막 실행 소요 시간 (초/밀리초 — 실행 이력 파생) | | extension_type | string | `core` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | | extension_identifier | string | `probe_6a71e0dc9a253` | 이 리소스를 소유한 확장의 식별자 | | creator | object | `{"uuid":"a26219fc-94a0-4f63-9404-04c2a6ac99e4","name":"최고…` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | | created_at | string | `2026-08-04 21:53:48` | 생성 일시 | | updated_at | string | `2026-08-04 21:53:48` | 최종 수정 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true,"c…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** ```http HTTP/1.1 201 ``` ```json { "success": true, "message": "스케줄이 생성되었습니다.", "data": { "id": 12, "name": "실측 예시값", "description": "실측 예시값", "type": "artisan", "type_label": "Artisan 커맨드", "command": "실측 예시값", "expression": "실측 예시값", "frequency": "everyMinute", "frequency_label": "매분", "without_overlapping": true, "run_in_maintenance": true, "timeout": 1, "is_active": true, "last_result": "never", "last_result_label": "미실행", "last_duration": null, "extension_type": "core", "extension_identifier": "probe_6a71e0dc9a253", "creator": { "uuid": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", "name": "최고관리자" }, "created_at": "2026-08-04 21:53:48", "updated_at": "2026-08-04 21:53:48", "abilities": { "can_create": true, "can_update": true, "can_delete": true, "can_run": true } } } ``` **에러 응답** | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`core.schedules.create`)이 없는 경우 | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | **설명** 새 예약 작업을 생성합니다. `core.schedules.create` 권한이 필요합니다. `type`(artisan/shell/url), `command`, `expression`, `frequency` 를 지정하며 생성자(creator)는 현재 사용자로 자동 기록됩니다. 검증 실패 시 422로 응답하고, 성공 시 201과 생성된 스케줄 리소스를 반환합니다. 관리자 스케줄 등록 폼에 사용합니다. ### DELETE /api/admin/schedules/bulk - **라우트명**: `api.admin.schedules.bulk-delete` - **컨트롤러**: `App\Http\Controllers\Api\Admin\ScheduleController@bulkDelete` - **인증/권한**: `auth:sanctum` + `permission:core.schedules.delete` **요청 파라미터** | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | ids | query | array | 예 | min 1 | 대상 리소스 식별자 배열 (대량 작업 대상) | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.schedule.bulk_delete_validation_rules`). **요청 예시** ```http DELETE /api/admin/schedules/bulk?ids=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} ``` **응답 필드** (`data` 내부) _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | deleted_count | integer | `3` | 실제로 삭제된 스케줄 건수 (`ScheduleService::bulkDelete` 반환값) | **응답 예시** ```http HTTP/1.1 200 ``` ```json { "success": true, "message": "선택한 스케줄이 삭제되었습니다.", "data": { "deleted_count": 3 } } ``` **에러 응답** | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`core.schedules.delete`)이 없는 경우 | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | **설명** 전달된 `ids` 배열의 스케줄을 일괄 삭제합니다. `core.schedules.delete` 권한이 필요합니다. 삭제 처리 결과 요약을 반환하며, 검증 실패 시 422로 응답합니다. 목록에서 여러 스케줄을 선택해 한 번에 제거하는 동작에 사용합니다. ### PATCH /api/admin/schedules/bulk-status - **라우트명**: `api.admin.schedules.bulk-status` - **컨트롤러**: `App\Http\Controllers\Api\Admin\ScheduleController@bulkUpdateStatus` - **인증/권한**: `auth:sanctum` + `permission:core.schedules.update` **요청 파라미터** | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | ids | body | array | 예 | min 1 | 대상 리소스 식별자 배열 (대량 작업 대상) | | is_active | body | boolean | 예 | — | 활성 여부 (true 활성 / false 비활성) | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.schedule.bulk_update_status_validation_rules`). **요청 예시** ```http PATCH /api/admin/schedules/bulk-status HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} Content-Type: application/json { "ids": [ "예시값" ], "is_active": true } ``` **응답 필드** (`data` 내부) _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | updated_count | integer | `3` | 상태가 실제로 변경된 스케줄 건수 (`ScheduleService::bulkUpdateStatus` 반환값) | **응답 예시** ```http HTTP/1.1 200 ``` ```json { "success": true, "message": "선택한 스케줄의 상태가 변경되었습니다.", "data": { "updated_count": 3 } } ``` **에러 응답** | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`core.schedules.update`)이 없는 경우 | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | **설명** 전달된 `ids` 배열의 스케줄 활성 상태(`is_active`)를 일괄 변경합니다. `core.schedules.update` 권한이 필요합니다. 처리 결과 요약을 반환하며, 검증 실패 시 422로 응답합니다. 목록에서 여러 스케줄을 선택해 한 번에 활성화/비활성화하는 동작에 사용합니다. ### DELETE /api/admin/schedules/history/{historyId} - **라우트명**: `api.admin.schedules.delete-history` - **컨트롤러**: `App\Http\Controllers\Api\Admin\ScheduleController@deleteHistory` - **인증/권한**: `auth:sanctum` + `permission:core.schedules.delete` **요청 파라미터** | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | historyId | path | string | 예 | — | 대상 history의 식별자 | **요청 예시** ```http DELETE /api/admin/schedules/history/{historyId} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} ``` **응답 필드** (`data` 내부) _이 엔드포인트는 `data` 를 반환하지 않습니다 (성공 메시지만 — 컨트롤러가 `success('schedule.history_delete_success')` 를 데이터 없이 호출)._ **응답 예시** ```http HTTP/1.1 200 ``` ```json { "success": true, "message": "실행 이력이 삭제되었습니다.", "data": null } ``` **에러 응답** | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`core.schedules.delete`)이 없는 경우 | | 404 | Not Found | 지정한 `historyId` 의 실행 이력이 존재하지 않는 경우 (`schedule.history_not_found`) | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | **설명** 특정 실행 이력 레코드를 삭제합니다. `core.schedules.delete` 권한이 필요합니다. 대상 이력이 없으면 404로 응답합니다. 스케줄 상세의 실행 이력 목록에서 개별 이력을 제거할 때 사용합니다. ### GET /api/admin/schedules/statistics - **라우트명**: `api.admin.schedules.statistics` - **컨트롤러**: `App\Http\Controllers\Api\Admin\ScheduleController@statistics` - **인증/권한**: `auth:sanctum` + `permission:core.schedules.read` **요청 파라미터** _요청 파라미터 없음._ **요청 예시** ```http GET /api/admin/schedules/statistics HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} ``` **응답 필드** (`data` 내부) _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | total | integer | `1` | 전체 개수 (집계) | | active | integer | `1` | 활성(is_active=true) 스케줄 수 | | inactive | integer | `0` | 비활성(is_active=false) 스케줄 수 | | success | integer | `1` | 마지막 실행 결과가 성공(success)인 스케줄 수 | | failed | integer | `0` | 마지막 실행 결과가 실패(failed)인 스케줄 수 | | running | integer | `0` | 마지막 실행 결과가 실행중(running)인 스케줄 수 | | never_run | integer | `0` | 아직 한 번도 실행되지 않은(last_result=never) 스케줄 수 | **응답 예시** ```http HTTP/1.1 200 ``` ```json { "success": true, "message": "스케줄 통계를 조회했습니다.", "data": { "total": 1, "active": 1, "inactive": 0, "success": 1, "failed": 0, "running": 0, "never_run": 0 } } ``` **에러 응답** | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`core.schedules.read`)이 없는 경우 | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | **설명** 스케줄 전체의 집계 통계를 조회합니다. `core.schedules.read` 권한이 필요합니다. 전체/활성/비활성 수와 마지막 실행 결과별(성공/실패/실행중/미실행) 건수를 반환합니다. 관리자 스케줄 대시보드의 요약 카드 표시에 사용합니다. ### DELETE /api/admin/schedules/{schedule} - **라우트명**: `api.admin.schedules.destroy` - **컨트롤러**: `App\Http\Controllers\Api\Admin\ScheduleController@destroy` - **인증/권한**: `auth:sanctum` + `permission:core.schedules.delete` **요청 파라미터** | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | schedule | path | string | 예 | — | 대상 schedule의 식별자 | **요청 예시** ```http DELETE /api/admin/schedules/{schedule} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} ``` **응답 필드** (`data` 내부) _이 엔드포인트는 `data` 를 반환하지 않습니다 (성공 메시지만 — 컨트롤러가 `success('schedule.delete_success')` 를 데이터 없이 호출)._ **응답 예시** ```http HTTP/1.1 200 ``` ```json { "success": true, "message": "스케줄이 삭제되었습니다.", "data": null } ``` **에러 응답** | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`core.schedules.delete`)이 없는 경우 | | 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | **설명** 단일 스케줄을 삭제합니다. `core.schedules.delete` 권한이 필요합니다. 경로의 `schedule` 은 라우트 모델 바인딩으로 해석되어 존재하지 않으면 404가 됩니다. 관리자 스케줄 관리 화면의 개별 삭제 동작에 사용합니다. ### GET /api/admin/schedules/{schedule} - **라우트명**: `api.admin.schedules.show` - **컨트롤러**: `App\Http\Controllers\Api\Admin\ScheduleController@show` - **인증/권한**: `auth:sanctum` + `permission:core.schedules.read` **요청 파라미터** | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | schedule | path | string | 예 | — | 대상 schedule의 식별자 | **요청 예시** ```http GET /api/admin/schedules/{schedule} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} ``` **응답 필드** (`data` 내부) _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | id | integer | `1` | 기본 키 (내부 식별자) | | name | string | `API 문서 샘플 스케줄` | 작업명 | | description | string | `문서 실측용 스케줄` | 설명 | | type | string | `artisan` | 작업 유형: artisan(Artisan 커맨드), shell(쉘 명령), url(URL 호출) | | type_label | string | `Artisan 커맨드` | `type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | command | string | `cache:clear` | 명령어 또는 URL | | expression | string | `0 3 * * *` | Cron 표현식 | | frequency | string | `daily` | 실행 주기: everyMinute(매분), hourly(매시간), daily(매일), weekly(매주), monthly(매월), custom(사용자 정의) | | frequency_label | string | `매일` | `frequency` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | without_overlapping | boolean | `true` | 중복 실행 방지 여부: 0(허용), 1(방지) | | run_in_maintenance | boolean | `false` | 점검 모드 실행 여부: 0(비실행), 1(실행) | | timeout | integer | `300` | 실행 제한 시간 (초) | | is_active | boolean | `true` | active 여부 | | last_result | string | `success` | 마지막 실행 결과: success(성공), failed(실패), running(실행중), never(미실행) | | last_result_label | string | `성공` | `last_result` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | last_run_at | string | `2026-07-30T22:55:00+09:00` | last run 일시 | | last_duration | null | `null` | 마지막 실행의 소요 시간을 사람이 읽는 문자열로 포맷한 값 (예: "45초", "2분 3초" — 마지막 실행 이력의 duration 파생, 실행 이력이 없으면 null) | | next_run_at | string | `2026-08-01T12:00:00+09:00` | next run 일시 | | extension_type | null | `null` | 확장 소유 타입: core(코어), module(모듈), plugin(플러그인), NULL(사용자 정의) | | extension_identifier | null | `null` | 확장 식별자 (예: core, sirsoft-board, sirsoft-payment) | | creator | object | `{"uuid":"a2640dae-e87c-4a28-b4f1-481fd961dc02","name":"AP…` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | | created_at | string | `2026-07-31 22:55:00` | 생성 일시 | | updated_at | string | `2026-07-31 22:55:00` | 최종 수정 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true,"c…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** ```http HTTP/1.1 200 ``` ```json { "success": true, "message": "스케줄 목록을 조회했습니다.", "data": { "id": 1, "name": "API 문서 샘플 스케줄", "description": "문서 실측용 스케줄", "type": "artisan", "type_label": "Artisan 커맨드", "command": "cache:clear", "expression": "0 3 * * *", "frequency": "daily", "frequency_label": "매일", "without_overlapping": true, "run_in_maintenance": false, "timeout": 300, "is_active": true, "last_result": "success", "last_result_label": "성공", "last_run_at": "2026-07-30T22:55:00+09:00", "last_duration": null, "next_run_at": "2026-08-01T12:00:00+09:00", "extension_type": null, "extension_identifier": null, "creator": { "uuid": "a2640dae-e87c-4a28-b4f1-481fd961dc02", "name": "API 문서 샘플 사용자" }, "created_at": "2026-07-31 22:55:00", "updated_at": "2026-07-31 22:55:00", "abilities": { "can_create": true, "can_update": true, "can_delete": true, "can_run": true } } } ``` **에러 응답** | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`core.schedules.read`)이 없는 경우 | | 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | **설명** 단일 스케줄의 상세 정보를 조회하며 생성자(creator) 정보를 함께 로드합니다. `core.schedules.read` 권한이 필요합니다. 경로의 `schedule` 은 라우트 모델 바인딩으로 해석되어 없으면 404가 됩니다. 관리자 스케줄 상세/수정 화면의 초기 데이터 로딩에 사용합니다. ### PUT /api/admin/schedules/{schedule} - **라우트명**: `api.admin.schedules.update` - **컨트롤러**: `App\Http\Controllers\Api\Admin\ScheduleController@update` - **인증/권한**: `auth:sanctum` + `permission:core.schedules.update` **요청 파라미터** | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | schedule | path | string | 예 | — | 대상 schedule의 식별자 | | name | body | string | 예 | max 255 | 대상의 이름/명칭 | | description | body | string | 아니오 | max 1000 | 설명 | | type | body | string | 예 | `artisan`, `shell`, `url` | 작업 유형: artisan(Artisan 커맨드 실행), shell(쉘 명령 실행), url(URL 호출) | | command | body | string | 예 | max 2000 | 실행할 명령 (`type` 에 따라 Artisan 커맨드 / 쉘 명령 / 호출할 URL). `type=url` 이면 내부 네트워크 주소(사설 IP·루프백·`localhost`·`*.internal` 등)는 거부되어 422 로 응답합니다 — 서버가 내부망으로 요청을 보내는 것을 막기 위함(SSRF). 사내 엔드포인트를 호출해야 하면 환경설정의 `security.allow_internal_outbound_urls` 를 켜세요. 이 설정을 켜도 userinfo(`https://a@b/`) 위장과 http/https 이외 scheme 은 계속 거부됩니다.
`type=shell` 이면 기본적으로 모든 명령이 거부되어 422 로 응답합니다 — 스케줄 권한만 위임받은 계정이 서버에서 임의 OS 명령을 실행하는 것을 막기 위함입니다. 실행하려면 서버의 `.env` 에서 `SCHEDULE_SHELL_ENABLED=true` 로 게이트를 열고 `SCHEDULE_SHELL_ALLOWED_BINARIES` 에 허용할 실행 파일명을 등록해야 합니다(관리자 화면에서는 넓힐 수 없는 코드 소유 정책). 허용된 실행 파일이라도 셸 메타문자(`|`, `;`, `$`, 백틱, 리다이렉션, 따옴표 등)가 포함되면 거부되며, 실행 시 셸을 경유하지 않습니다. 복합 명령이 필요하면 래퍼 스크립트를 만들어 화이트리스트에 등록하세요.
`type=artisan` 이면 서버가 허용한 유지보수 명령만 등록할 수 있고 그 밖의 명령은 거부되어 422 로 응답합니다 — 캐시·컴파일 산출물 정리(`cache:clear` 등), 큐 처리(`queue:work`·`queue:restart` 등), 만료 데이터 정리, SEO 관련 명령이 여기 해당합니다. `tinker` 처럼 임의 코드를 실행하거나 `migrate`·`db:wipe`·`make:*` 처럼 스키마를 바꾸거나 코드를 생성하는 명령은 허용 목록에 오르지 않습니다(관리자 화면에서는 넓힐 수 없는 코드 소유 정책). 설치된 모듈·플러그인이 제공하는 명령은 자동으로 허용됩니다.
허용된 명령이라도 그 명령이 선언하지 않은 옵션, 위치 인자, 단축 옵션(`-v`)이 붙으면 거부됩니다. 형식은 `명령명 --옵션[=값]` 만 허용하며 따옴표·역슬래시가 포함된 입력은 거부됩니다 — 저장할 때 해석한 명령과 실제로 실행되는 명령이 달라지지 않도록 하기 위함입니다. 거부 사유(허용 목록 밖 / 형식 오류 / 허용되지 않은 옵션 / 추가 인자 불가)는 응답 메시지로 구분되어 안내됩니다 | | expression | body | string | 예 | max 100 | 실행 시각을 정의하는 Cron 표현식 (예: `0 3 * * *`, 다음 실행 시각 next_run_at 계산의 기준) | | frequency | body | string | 예 | `everyMinute`, `hourly`, `daily`, `weekly`, `monthly`, `custom` | 실행 주기 | | without_overlapping | body | boolean | 아니오 | — | 중복 실행 방지 여부 | | run_in_maintenance | body | boolean | 아니오 | — | 점검 모드 중 실행 여부 | | timeout | body | integer | 아니오 | min 1, max 86400 | 타임아웃 (초) | | is_active | body | boolean | 아니오 | — | 활성 여부 (true 활성 / false 비활성) | | extension_type | body | string | 아니오 | `core`, `module`, `plugin` | 확장 유형 (core/module/plugin/template) | | extension_identifier | body | string | 아니오 | max 255 | 확장 식별자 | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.schedule.update_validation_rules`). **요청 예시** ```http PUT /api/admin/schedules/{schedule} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} Content-Type: application/json { "name": "예시 이름", "description": "예시 내용입니다.", "type": "artisan", "command": "예시값", "expression": "예시값", "frequency": "everyMinute", "without_overlapping": true, "run_in_maintenance": true, "timeout": 1, "is_active": true, "extension_type": "core", "extension_identifier": "example-key" } ``` **응답 필드** (`data` 내부) _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | id | integer | `1` | 기본 키 (내부 식별자) | | name | string | `실측 예시값` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | | description | string | `실측 예시값` | 설명 (다국어 필드는 로케일별 값 객체) | | type | string | `artisan` | 작업 유형: artisan(Artisan 커맨드), shell(쉘 명령), url(URL 호출) | | type_label | string | `Artisan 커맨드` | `type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | command | string | `실측 예시값` | 명령어 또는 URL | | expression | string | `실측 예시값` | Cron 표현식 | | frequency | string | `everyMinute` | 실행 주기: everyMinute(매분), hourly(매시간), daily(매일), weekly(매주), monthly(매월), custom(사용자 정의) | | frequency_label | string | `매분` | `frequency` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | without_overlapping | boolean | `true` | 중복 실행 방지 여부: 0(허용), 1(방지) | | run_in_maintenance | boolean | `true` | 점검 모드 실행 여부: 0(비실행), 1(실행) | | timeout | integer | `1` | 실행 제한 시간 (초) | | is_active | boolean | `true` | active 여부 | | last_result | string | `success` | 마지막 실행 결과: success(성공), failed(실패), running(실행중), never(미실행) | | last_result_label | string | `성공` | `last_result` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | last_run_at | string | `2026-07-30T22:55:00+09:00` | last run 일시 | | last_duration | null | `null` | 마지막 실행 소요 시간 (초/밀리초 — 실행 이력 파생) | | extension_type | string | `core` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | | extension_identifier | string | `probe_6a71e0dceb9a2` | 이 리소스를 소유한 확장의 식별자 | | creator | object | `{"uuid":"a2640dae-e87c-4a28-b4f1-481fd961dc02","name":"AP…` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | | created_at | string | `2026-07-31 22:55:00` | 생성 일시 | | updated_at | string | `2026-08-04 21:53:48` | 최종 수정 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true,"c…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** ```http HTTP/1.1 200 ``` ```json { "success": true, "message": "스케줄이 수정되었습니다.", "data": { "id": 1, "name": "실측 예시값", "description": "실측 예시값", "type": "artisan", "type_label": "Artisan 커맨드", "command": "실측 예시값", "expression": "실측 예시값", "frequency": "everyMinute", "frequency_label": "매분", "without_overlapping": true, "run_in_maintenance": true, "timeout": 1, "is_active": true, "last_result": "success", "last_result_label": "성공", "last_run_at": "2026-07-30T22:55:00+09:00", "last_duration": null, "extension_type": "core", "extension_identifier": "probe_6a71e0dceb9a2", "creator": { "uuid": "a2640dae-e87c-4a28-b4f1-481fd961dc02", "name": "API 문서 샘플 사용자" }, "created_at": "2026-07-31 22:55:00", "updated_at": "2026-08-04 21:53:48", "abilities": { "can_create": true, "can_update": true, "can_delete": true, "can_run": true } } } ``` **에러 응답** | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`core.schedules.update`)이 없는 경우 | | 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | **설명** 기존 스케줄 정보를 수정합니다. `core.schedules.update` 권한이 필요합니다. store 와 동일한 필드(`type`/`command`/`expression`/`frequency` 등)를 받으며 검증 실패 시 422로 응답합니다. 경로의 `schedule` 은 라우트 모델 바인딩으로 해석되며, 수정된 스케줄 리소스를 반환합니다. 관리자 스케줄 수정 폼에 사용합니다. ### POST /api/admin/schedules/{schedule}/duplicate - **라우트명**: `api.admin.schedules.duplicate` - **컨트롤러**: `App\Http\Controllers\Api\Admin\ScheduleController@duplicate` - **인증/권한**: `auth:sanctum` + `permission:core.schedules.create` **요청 파라미터** | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | schedule | path | string | 예 | — | 대상 schedule의 식별자 | **요청 예시** ```http POST /api/admin/schedules/{schedule}/duplicate HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} ``` **응답 필드** (`data` 내부) _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | id | integer | `13` | 기본 키 (내부 식별자) | | name | string | `API 문서 샘플 스케줄 (복사본)` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | | description | string | `문서 실측용 스케줄` | 설명 (다국어 필드는 로케일별 값 객체) | | type | string | `artisan` | 작업 유형: artisan(Artisan 커맨드), shell(쉘 명령), url(URL 호출) | | type_label | string | `Artisan 커맨드` | `type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | command | string | `cache:clear` | 명령어 또는 URL | | expression | string | `0 3 * * *` | Cron 표현식 | | frequency | string | `daily` | 실행 주기: everyMinute(매분), hourly(매시간), daily(매일), weekly(매주), monthly(매월), custom(사용자 정의) | | frequency_label | string | `매일` | `frequency` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | without_overlapping | boolean | `true` | 중복 실행 방지 여부: 0(허용), 1(방지) | | run_in_maintenance | boolean | `false` | 점검 모드 실행 여부: 0(비실행), 1(실행) | | timeout | integer | `300` | 실행 제한 시간 (초) | | is_active | boolean | `false` | active 여부 | | last_result | string | `never` | 마지막 실행 결과: success(성공), failed(실패), running(실행중), never(미실행) | | last_result_label | string | `미실행` | `last_result` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | last_duration | null | `null` | 마지막 실행 소요 시간 (초/밀리초 — 실행 이력 파생) | | extension_type | null | `null` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | | extension_identifier | null | `null` | 이 리소스를 소유한 확장의 식별자 | | created_at | string | `2026-08-04 21:53:48` | 생성 일시 | | updated_at | string | `2026-08-04 21:53:48` | 최종 수정 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true,"c…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** ```http HTTP/1.1 201 ``` ```json { "success": true, "message": "스케줄이 복제되었습니다.", "data": { "id": 13, "name": "API 문서 샘플 스케줄 (복사본)", "description": "문서 실측용 스케줄", "type": "artisan", "type_label": "Artisan 커맨드", "command": "cache:clear", "expression": "0 3 * * *", "frequency": "daily", "frequency_label": "매일", "without_overlapping": true, "run_in_maintenance": false, "timeout": 300, "is_active": false, "last_result": "never", "last_result_label": "미실행", "last_duration": null, "extension_type": null, "extension_identifier": null, "created_at": "2026-08-04 21:53:48", "updated_at": "2026-08-04 21:53:48", "abilities": { "can_create": true, "can_update": true, "can_delete": true, "can_run": true } } } ``` **에러 응답** | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`core.schedules.create`)이 없는 경우 | | 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | **설명** 기존 스케줄을 복제해 새 스케줄을 만듭니다. `core.schedules.create` 권한이 필요합니다. 원본을 바탕으로 새 레코드를 생성하며, 성공 시 201과 복제된 스케줄 리소스를 반환합니다. 유사한 설정의 스케줄을 빠르게 추가하는 "복제" 동작에 사용합니다. ### GET /api/admin/schedules/{schedule}/history - **라우트명**: `api.admin.schedules.history` - **컨트롤러**: `App\Http\Controllers\Api\Admin\ScheduleController@history` - **인증/권한**: `auth:sanctum` + `permission:core.schedules.read` **요청 파라미터** | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | schedule | path | string | 예 | — | 대상 schedule의 식별자 | | page | query | integer | 아니오 | min 1 | 조회할 페이지 번호 (1부터 시작) | | per_page | query | integer | 아니오 | min 1, max 100 | 페이지당 항목 수 | | status | query | string | 아니오 | `success`, `failed`, `running` | 상태 필터 (해당 상태의 항목만 조회) | | trigger_type | query | string | 아니오 | `scheduled`, `manual` | 실행 방식 필터: scheduled(예약 시각 자동 실행), manual(관리자의 즉시 실행) 중 해당 이력만 조회 | | started_from | query | date | 아니오 | — | 실행 시작일 범위의 시작일 (이 날짜 이후 시작된 이력만 조회) | | started_to | query | date | 아니오 | — | 실행 시작일 범위의 종료일 (이 날짜 이전 시작된 이력만 조회, started_from 이후여야 함) | | sort_by | query | string | 아니오 | `started_at`, `ended_at`, `duration`, `status` | 정렬 기준 필드명 | | sort_order | query | string | 아니오 | `asc`, `desc` | 정렬 방향 (asc 오름차순 / desc 내림차순) | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.schedule.history_list_validation_rules`). **요청 예시** ```http GET /api/admin/schedules/{schedule}/history?page=1&per_page=1&status=success&trigger_type=scheduled&started_from=2026-01-01&started_to=2026-01-01&sort_by=started_at&sort_order=asc HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} ``` **응답 필드** (`data` 내부) _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ 목록 항목은 `ScheduleHistoryResource::toListArray()` 산물입니다 (단건 실행 응답과 달리 `output`/`error_output`/`memory_usage`(raw)/`created_at`/`updated_at` 은 포함되지 않습니다). | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | id | integer | `7` | 실행 이력의 기본 키 (내부 식별자) | | schedule_id | integer | `1` | 이 이력이 속한 스케줄의 식별자 | | started_at | string\|null | `2026-07-08T12:14:49+09:00` | 실행 시작 일시 (사용자 타임존 포맷) | | ended_at | string\|null | `2026-07-08T12:14:49+09:00` | 실행 종료 일시 (사용자 타임존 포맷, 실행 중이면 null) | | duration | integer\|null | `0` | 실행 소요 시간 | | duration_formatted | string\|null | `45초` | `duration` 값의 표시용 포맷 문자열 (모델 접근자 파생, 없으면 null) | | status | string | `success` | 실행 결과 상태: success(성공), failed(실패), running(실행중) | | status_label | string\|null | `성공` | `status` 값의 사람이 읽는 라벨 (`schedule.result.*` 현지화) | | exit_code | integer\|null | `0` | 실행 종료 코드 (0=성공, 그 외=실패) | | memory_usage_formatted | string\|null | `417.34 KB` | 실행 중 메모리 사용량의 표시용 포맷 문자열 | | trigger_type | string | `manual` | 실행 방식: scheduled(예약 자동 실행), manual(수동 즉시 실행) | | trigger_type_label | string\|null | `수동 실행` | `trigger_type` 값의 사람이 읽는 라벨 (`schedule.trigger_type.*` 현지화) | | triggered_by | object\|null | `{"uuid":"a234c2b1-cde8-437f-b28b-23323be2b98d","name":"관리자"}` | 수동 실행을 트리거한 사용자 정보 (uuid/name — triggeredBy 관계가 로드되지 않았거나 자동 실행이면 null) | `data.pagination` 은 `current_page`/`last_page`/`per_page`/`total`/`from`/`to`/`has_more_pages` 로 구성됩니다. **응답 예시** ```http HTTP/1.1 200 ``` ```json { "success": true, "message": "실행 이력을 조회했습니다.", "data": { "data": [ { "id": 7, "schedule_id": 1, "started_at": "2026-07-08T12:14:49+09:00", "ended_at": "2026-07-08T12:14:49+09:00", "duration": 0, "duration_formatted": null, "status": "success", "status_label": "성공", "exit_code": 0, "memory_usage_formatted": "417.34 KB", "trigger_type": "manual", "trigger_type_label": "수동 실행", "triggered_by": null } ], "pagination": { "current_page": 1, "last_page": 1, "per_page": 25, "total": 1, "from": 1, "to": 1, "has_more_pages": false } } } ``` **에러 응답** | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`core.schedules.read`)이 없는 경우 | | 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | **설명** 특정 스케줄의 실행 이력을 페이지네이션으로 조회합니다. `core.schedules.read` 권한이 필요합니다. `status`(success/failed/running), `trigger_type`(scheduled/manual), 기간(`started_from`/`started_to`)으로 필터링하고 `sort_by`/`sort_order` 로 정렬할 수 있습니다. 스케줄 상세의 실행 이력 탭 표시에 사용합니다. ### POST /api/admin/schedules/{schedule}/run - **라우트명**: `api.admin.schedules.run` - **컨트롤러**: `App\Http\Controllers\Api\Admin\ScheduleController@run` - **인증/권한**: `auth:sanctum` + `permission:core.schedules.run` **요청 파라미터** | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | schedule | path | string | 예 | — | 대상 schedule의 식별자 | **요청 예시** ```http POST /api/admin/schedules/{schedule}/run HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} ``` **응답 필드** (`data` 내부) _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | id | integer | `6` | 기본 키 (내부 식별자) | | schedule_id | integer | `1` | schedule 식별자 (연관 리소스 참조) | | started_at | string | `2026-08-04T21:53:49+09:00` | started 일시 | | ended_at | string | `2026-08-04T21:53:49+09:00` | ended 일시 | | duration | integer | `0` | 실행 소요 시간 (초/밀리초) | | duration_formatted | null | `null` | `duration` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | status | string | `success` | 상태 값 (도메인별 상태 집합 — 사람이 읽는 라벨은 status_label, UI 변형은 status_variant 참조) | | status_label | string | `성공` | 상태의 사람이 읽는 라벨 (상태 Enum label() 산물) | | exit_code | integer | `0` | 실행 종료 코드 (0=성공, 그 외=실패) | | memory_usage | integer | `427312` | 실행 중 최대 메모리 사용량 (바이트) | | memory_usage_formatted | string | `417.3 KB` | `memory_usage` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | output | string | ` INFO Application cache cleared s…` | 실행 표준 출력 내용 | | error_output | null | `null` | 실행 표준 에러 출력 내용 (없으면 빈 문자열/null) | | trigger_type | string | `manual` | 동작을 유발한 방식/주체 구분 값 | | trigger_type_label | string | `수동 실행` | `trigger_type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | created_at | string | `2026-08-04 21:53:49` | 생성 일시 | | updated_at | string | `2026-08-04 21:53:49` | 최종 수정 일시 | **응답 예시** ```http HTTP/1.1 200 ``` ```json { "success": true, "message": "스케줄이 실행되었습니다.", "data": { "id": 6, "schedule_id": 1, "started_at": "2026-08-04T21:53:49+09:00", "ended_at": "2026-08-04T21:53:49+09:00", "duration": 0, "duration_formatted": null, "status": "success", "status_label": "성공", "exit_code": 0, "memory_usage": 427312, "memory_usage_formatted": "417.3 KB", "output": "\n INFO Application cache cleared successfully. \n\r\n", "error_output": null, "trigger_type": "manual", "trigger_type_label": "수동 실행", "created_at": "2026-08-04 21:53:49", "updated_at": "2026-08-04 21:53:49" } } ``` **에러 응답** | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`core.schedules.run`)이 없는 경우 | | 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | **설명** 스케줄을 예약 시각과 무관하게 즉시 실행합니다. `core.schedules.run` 권한이 필요합니다. 실제 명령이 수행되고 실행 이력 레코드가 생성되며, 그 이력(trigger_type=manual)을 반환합니다. 관리자가 대상 작업을 수동으로 즉시 돌려 결과를 확인하는 "지금 실행" 동작에 사용합니다.