# 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)을 반환합니다. 관리자가 대상 작업을 수동으로 즉시 돌려 결과를 확인하는 "지금 실행" 동작에 사용합니다.