스크린 타임
이 가이드는 스크린 타임 통합을 다룹니다. 플레이어가 제품을 사용하고 있음을 보고하는 방법, 판정을 읽는 방법, 일곱 가지 스크린 타임 웹훅 이벤트를 처리하는 방법, 그리고 플레이어가 부모에게 추가 시간을 요청할 수 있게 하는 방법을 설명합니다.
스크린 타임이 무엇이고 k-ID와 제품 사이의 책임 경계가 어디인지는 먼저 스크린 타임 개념을 읽어보세요.
시작하기 전에
- 조직에서 스크린 타임을 사용할 수 있는지 확인하세요. 이 기능을 조직에서 활성화하려면 k-ID에 문의하세요.
- 제품에서 기능을 활성화하세요. 스크린 타임은 기본적으로 꺼져 있으며, Compliance Studio에서 두 개의 스위치를 다음 순서로 켜야 합니다. 먼저 Screentime을 켜고, 그다음 Screentime Controls를 켠 뒤 변경을 게시하세요. Controls는 Screentime에 의존하므로 Screentime이 켜지기 전까지는 조작할 수 없습니다. 해당 엔드포인트가 필요로 하는 스위치가 켜지기 전까지 그 엔드포인트는
FEATURE_DISABLED로 실패하며, 두 엔드포인트 그룹은 서로 다른 스위치를 필요로 합니다. 기능 플래그를 참조하세요. - 웹훅 엔드포인트를 설정하세요. 일곱 가지 스크린 타임 신호는 모두 웹훅으로 도착합니다. 제품의 Developer Settings에서 URL과 시크릿을 설정하고, 웹훅 개요의 설명에 따라 서명을 검증하세요.
- 처리할 이벤트를 구독하세요. 엔드포인트는 구독한 이벤트 유형만 수신하며, k-ID는 나머지를 전달 시도나 오류 없이 버립니다. Compliance Studio에서 제품의 Developer Settings 페이지로 이동해 엔드포인트에 대해
Screentime.BreakReminder,Screentime.LimitWarning,Screentime.LimitReached,Screentime.ScheduleChanged,Screentime.QuietHoursWarning,Screentime.QuietHoursReached,Screentime.OverrideResult를 선택하세요. 이 항목들은 조직에서 스크린 타임이 활성화된 뒤에야 목록에 표시됩니다. kuid가 있는 플레이어의 세션이 있어야 합니다. 모든 스크린 타임 호출은 k-IDsessionId를 대상으로 하지만, 그 뒤에 있는 일정은 세션이 아니라 플레이어의kuid에 저장됩니다.kuid가 없는 세션은 일정을 가질 수 없으므로, 부모가 어떻게 설정하더라도GET /screentime/get-state는{ "enabled": false }를 반환합니다. 플레이어는 신뢰할 수 있는 성인이 동의를 완료한 시점에kuid를 갖게 되므로, 먼저 그 흐름을 진행하고 이후 세션에서는 저장한kuid를 다시 사용하세요. 세션 및 권한과 Challenge를 참조하세요.
부모가 일정을 설정하지 않은 플레이어는 오류가 아닙니다. GET /screentime/get-state는 { "enabled": false }를 반환하고, 어떤 판정도 차단하지 않으며 이벤트도 발생하지 않습니다. 이 경우를 전제로 구현하세요. 대부분의 플레이어가 이 상태입니다.
사용 시간 보고
사용 시간을 보고하는 방법은 두 가지이며, 이 선택은 겉모습의 차이가 아닙니다. 제품이 실시간 신호를 받을 수 있는지가 이것으로 결정됩니다.
진행 중인 세션: start와 end | 사후 보고: push | |
|---|---|---|
| 엔드포인트 | /screentime/start, /screentime/end | /screentime/push |
| 일일 합계에 반영 | 됨 | 됨 |
Screentime.BreakReminder | 발생함 | 발생하지 않음 |
Screentime.LimitWarning | 발생함 | 발생하지 않음 |
Screentime.LimitReached | 발생함 | 발생하지 않음 |
| 보고 가능 범위 | 실시간, 해당 시점으로부터 5분 이내 | 지난 7일 이내의 임의 시점 |
| 적합한 경우 | 제품이 플레이어의 시작과 중지를 알 수 있는 경우 | 제품이 세션이 끝난 뒤에야 사용 시간을 아는 경우 |
start와 end를 선택하세요이것은 엔드포인트 목록만 보고는 추측할 수 없는 스크린 타임의 핵심입니다. /screentime/push로 보고된 사용 시간은 일일 합계에 더해지고 부모의 Family Connect 차트에도 표시되지만, 휴식 알림도 한도 경고도 한도 도달 이벤트도 발생하지 않습니다.
이유는 시점입니다. k-ID는 /screentime/start를 호출할 때 알림과 경고를 예약하므로 그것들은 플레이어가 아직 세션 중일 때 발생합니다. 90분 세션을 사후에 보고하면 60분 시점의 휴식 알림은 30분 늦게 발생할 수밖에 없고, 그것은 휴식 알림으로 기능하지 않습니다. 제품이 휴식 알림 의무를 지고 있다면 /screentime/push로는 이를 충족할 수 없습니다.
조용한 시간 이벤트와 Screentime.ScheduleChanged는 세션 보고 방식이 아니라 부모의 달력에 의해 구동되므로 어느 방식에서도 발생합니다.
세션 상태 머신
sessionId당 동시에 진행할 수 있는 스크린 타임 세션은 하나뿐이며, 다음 규칙은 여기에서 비롯됩니다.
id는 제품에서 생성합니다./screentime/start에는 새 UUID를 보내고/screentime/end에는 같은id를 보내세요. 진행 중인 세션과 일치하지 않는id로 보낸end는NOT_FOUND를 반환합니다.- 재시도는 안전합니다. 같은
id로start를 반복하면 중복 집계 없이accepted가 반환됩니다. 이미 종료된 세션에 대해end를 반복해도accepted가 반환됩니다. - 다른
id로 보낸 두 번째start는status: "replaced"를 반환합니다. k-ID는 이전 세션을 종료하고 그 시간을 집계한 뒤 새 세션을 진행 중으로 만듭니다. 앱이 강제 종료되거나 기기가 재시작되어end가 도착하지 않은 경우의 복구 경로가 바로 이것입니다.start를 다시 호출하고replaced인지 확인하면 됩니다. 같은 플레이어가 두 번째 기기에서 플레이를 시작할 때도 같은 일이 벌어지며, 그 경우에는 부모가 볼 수 있는 결과가 따릅니다. 첫 번째 기기는 집계가 멈추고 알림도 더 이상 받지 못합니다. 사용 시간 집계 방식을 참조하세요. - k-ID는 4시간이 지난 세션을 자동으로 종료합니다. 그 시점까지의 시간은 집계되고 대기 중인 알림은 취소됩니다. 이는
end를 아예 보내지 않게 된 제품의 영향 범위를 제한하는 장치입니다.end를 호출하는 것을 대신하는 것이 아니라 안전망입니다. - 타임스탬프는 실시간이어야 합니다.
/screentime/start는 5분보다 이전의timestamp와 미래의timestamp를 거부합니다./screentime/end는 start의 타임스탬프 이후여야 하며, 약 1분 정도의 앞선 시계 오차를 허용합니다.
start와 end는 모두 응답에 카운터를 반환합니다. 따라서 세션의 시작이나 끝에 플레이어에게 남은 시간을 표시하기 위해 GET /screentime/get-state를 따로 호출할 필요가 없습니다.
{
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"id": "3f8c1d24-6b5e-4a07-9c2f-8d1e7a4b6c90",
"status": "accepted",
"screentime": {
"counter": {
"timeUsedTodayMinutes": 45,
"timeLimitTodayMinutes": 120,
"timeRemainingTodayMinutes": 75,
"dayResetsAt": "2026-06-25T00:00:00Z"
}
}
}
사후 보고
/screentime/push는 한 번의 호출로 최대 100개의 완료된 구간을 받습니다. 각 구간은 멱등성을 위한 자체 id, 사용이 시작된 시점의 timestamp, 그리고 durationSeconds를 가집니다.
구간은 각각 독립적으로 처리되므로 배치 전체가 성공했다고 가정하지 말고 이벤트별 상태를 읽으세요.
{
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"accepted": 1,
"rejected": 1,
"events": [
{ "id": "9d4e2f81-7a3b-4c56-8e90-1f2a3b4c5d6e", "status": "accepted" },
{ "id": "c1b2a3d4-5e6f-4708-9a1b-2c3d4e5f6071", "status": "rejected", "reason": "invalid_input" }
]
}
reason이 invalid_input이면 그 구간은 검증 규칙을 위반한 것이므로 그대로 재시도해도 다시 실패합니다. 구간은 이미 완료되어 있어야 하고, timestamp는 지난 7일 이내여야 하며, durationSeconds는 1초 이상 24시간 이하여야 합니다. reason이 internal_error이면 일시적인 문제이므로 재시도해도 안전합니다. 이미 수락된 id를 다시 보내면 중복 집계 없이 다시 accepted가 됩니다.
상태 읽기
GET /screentime/get-state는 규칙과 현재 판정에 대한 단일 진실 공급원입니다. 세션이 시작될 때 호출하고, 상황을 바꾸는 이벤트를 받은 뒤에도 호출하세요.
{
"enabled": true,
"access": {
"allowed": false,
"details": {
"reason": "quiet_hours",
"resumesAt": "2026-06-25T07:00:00+09:00"
}
},
"state": {
"timeUsedTodayMinutes": 75,
"continuousUsageMinutes": 30,
"timeLimitTodayMinutes": 120,
"timeRemainingTodayMinutes": 45,
"dayResetsAt": "2026-06-25T00:00:00Z"
},
"schedule": {
"timezone": "Asia/Seoul",
"breakReminderIntervalMinutes": 45,
"dailyLimits": [{ "day": "mon", "limitMinutes": 120 }],
"quietHours": [
{
"name": "Bedtime",
"days": ["mon", "tue", "wed", "thu", "fri"],
"start": "21:00",
"end": "07:00"
}
]
}
}
다음 순서로 읽으세요.
enabled.false일 때 응답에는 다른 내용이 없으며, 이유는 세 가지입니다. 부모가 스크린 타임을 설정하지 않았거나, 부모가 설정했지만 제한을 껐거나, 세션에kuid가 없어 일정이 속할 플레이어가 없는 경우입니다. 테스트에서 가장 자주 마주치는 것은 세 번째입니다. 세 경우 모두 아무것도 제한하지 마세요.access.allowed. 이것이 판정이며, 일일 한도와 모든 조용한 시간대가 이미 결합되어 있습니다.schedule에서 직접 다시 계산하지 마세요.access.details.allowed가false일 때만 포함됩니다.reason은quiet_hours또는limit_reached이고,resumesAt은 제한이 해제되는 시각으로 플레이어에게 알려주면 유용한 정보입니다. 두 제한이 동시에 적용되는 경우에는 더 늦게 끝나는 쪽이 반환됩니다.state. 제품 UI에 쓸 오늘의 수치입니다.timeUsedTodayMinutes는 진행 중인 세션의 시간을 포함하고,continuousUsageMinutes는 그 세션만의 길이입니다. 오늘 요일에 한도가 설정되어 있지 않으면timeLimitTodayMinutes와timeRemainingTodayMinutes는 포함되지 않습니다.schedule. 표시용 부모의 규칙입니다. 시각은schedule.timezone기준HH:MM입니다.
이벤트 처리
이 이벤트들은 제품의 웹훅이 해당 이벤트 유형을 구독한 경우에만 도착합니다. 한 번도 실행되지 않는 핸들러를 디버깅하기 전에 시작하기 전에를 확인하세요.
| 이벤트 | 의미 | 대응 |
|---|---|---|
Screentime.BreakReminder | 플레이어가 설정된 간격 동안 하나의 세션을 계속했습니다 | 휴식을 권하는 안내를 표시합니다 |
Screentime.LimitWarning | 오늘 15분 또는 5분이 남았습니다 | 저장 지점에 도달할 수 있도록 플레이어에게 알립니다 |
Screentime.LimitReached | 오늘의 한도가 적용되었습니다 | 제한을 적용하고 부모에게 요청할 방법을 제시합니다 |
Screentime.QuietHoursWarning | 15분 후에 시간대가 시작됩니다 | 어떤 시간대가 언제 시작되는지 알립니다 |
Screentime.QuietHoursReached | 시간대가 시작되었습니다 | endsAt까지 제한을 적용합니다 |
Screentime.ScheduleChanged | 부모가 규칙을 변경했습니다 | get-state를 다시 가져와 보관 중인 상태를 교체합니다 |
Screentime.OverrideResult | 부모가 예외 요청에 답변했습니다 | 페이로드의 state를 적용합니다 |
전달과 관련해 설계에서 고려할 성질이 세 가지 있습니다.
- 이벤트는
sessionId를 대상으로 전달됩니다. 일곱 가지 모두에 포함된data.sessionId로 분기하세요. - 전달은 최소 한 번입니다. 같은 이벤트가 두 번 도착할 수 있습니다. 웹훅 개요의 설명대로 핸들러를 멱등하게 만드세요.
- 조용한 시간 이벤트는 진행 중인 세션을 필요로 하지 않습니다. 제품에 유효한 k-ID 세션이 있는 플레이어에게, 그 시점에 플레이하고 있는지와 무관하게 부모의 달력에 따라 발생합니다. 끼어들 UI가 있다고 가정하지 마세요.
부모에게 추가 시간 요청하기
access.allowed가 false일 때는 막힌 벽만 보여주는 대신 요청할 방법을 플레이어에게 제시하세요.
sessionId와 함께/screentime/request-override를 호출합니다. 응답에는id와expiresAt이 담깁니다.id를 저장하세요.- k-ID가 부모에게 알리고, 부모는 Family Connect에서 승인하거나 거부합니다.
- 결과는
data.id로 연결되는Screentime.OverrideResult로 도착하며status는granted,denied,expired중 하나입니다. data.state를 읽으세요. 결정 후에 계산된get-state와 같은 형태입니다. 추가 조회는 필요하지 않습니다.
이 엔드포인트는 플레이어별로 멱등합니다. 요청이 아직 대기 중인 동안 다시 호출하면 두 번째 요청을 만들지 않고 대기 중인 요청을 그대로 반환하며, 응답 형태는 어느 경우에도 동일하므로 별도의 코드 경로가 필요하지 않습니다. 답변이 없는 요청은 24시간 후 만료되어 status: "expired"로 웹훅이 발생합니다. 그 후 플레이어는 다시 요청할 수 있습니다.
승인된 예외는 오늘의 한도를 올립니다. 일정을 비활성화하지 않으며 다음 날로 이어지지도 않습니다.
시간대와 날짜 경계
모든 일정에는 시간대가 있고 스크린 타임의 모든 규칙은 그 시간대를 기준으로 평가됩니다. 타임스탬프는 오프셋이 있는 RFC 3339로 보내고 변환은 k-ID에 맡기세요.
- 일일 한도는 일정의 시간대 기준 현지 시간 자정에 초기화됩니다. 상태 응답의
dayResetsAt이 그 시각입니다. - 현지 시간 자정을 넘는 세션은
/screentime/end에서 분할되므로 각 날짜의 합계는 그날 실제로 발생한 사용 시간을 반영합니다. 자정을 넘는 push 구간도 마찬가지입니다.
기능 플래그
스크린 타임에는 두 개의 계층이 있으며, 제품은 첫 번째 계층만 활성화할 수도 있습니다.
| 플래그 | 활성화하는 기능 | 적용 엔드포인트 |
|---|---|---|
| Screentime | 수동적인 사용 시간 보고, 부모 설정 불필요 | /screentime/push |
| Screentime Controls | 부모가 설정하는 한도, 일정, 조용한 시간, 휴식 알림, 예외 | /screentime/start, /screentime/end, /screentime/get-state, /screentime/request-override |
Screentime만 활성화하면 제품은 사용 시간을 보고하는 것만 할 수 있습니다. /screentime/push로 완료된 구간을 보내면 자녀의 하루 합계에 반영됩니다. 이는 활동 수집과 같은 형태의 통합이며, 부모가 무언가를 설정할 필요가 없습니다.
Screentime Controls는 두 번째 계층으로, 부모가 설정하는 일정과 그에 따라 동작하는 모든 것입니다. 세션 상태 머신(/screentime/start, /screentime/end), 현재 판정(/screentime/get-state), 부모에게 요청하는 흐름(/screentime/request-override)을 제어하며, 실시간 이벤트는 그 세션 머신에서 생성됩니다.
계층이 꺼진 엔드포인트는 FEATURE_DISABLED를 반환합니다. Screentime만 활성화한 제품은 사용 시간을 push할 수 있지만 세션을 시작하거나 상태를 읽을 수는 없으며, 둘 다 활성화하지 않은 제품은 어떤 엔드포인트도 호출할 수 없습니다.
출시 전 점검 목록
- 모든 세션의 시작 시점에
GET /screentime/get-state를 호출하며,enabled: false인 경우 플레이어를 제한하지 않습니다. schedule에서 판정을 다시 계산하지 않고access.allowed를 읽습니다.- 모든
/screentime/start가 같은id의/screentime/end와 짝을 이루며,status가replaced인 경우를 오류로 처리하지 않고 다룹니다. - 각 경고 쌍에서 두 번째 이벤트만이 아니라 두 이벤트를 모두 처리합니다.
- 제품의 웹훅이 처리하려는 일곱 가지
Screentime.*이벤트 유형을 각각 구독하고 있습니다. 웹훅이 선택하지 않은 이벤트 유형은 전달되지 않습니다. - 웹훅 핸들러가 멱등하며 서명을 검증합니다.
Screentime.ScheduleChanged를 받으면get-state를 다시 가져옵니다.- 한도나 조용한 시간대에 도달한 플레이어에게
/screentime/request-override를 제시하며,expired를 포함한 세 가지 예외 상태를 모두 처리합니다. - 제품이 적용하는 제한이
resumesAt또는endsAt을 사용해 해제되는 시각을 플레이어에게 알려줍니다.