본문으로 건너뛰기

스크린 타임

이 가이드는 스크린 타임 통합을 다룹니다. 플레이어가 제품을 사용하고 있음을 보고하는 방법, 판정을 읽는 방법, 일곱 가지 스크린 타임 웹훅 이벤트를 처리하는 방법, 그리고 플레이어가 부모에게 추가 시간을 요청할 수 있게 하는 방법을 설명합니다.

스크린 타임이 무엇이고 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-ID sessionId를 대상으로 하지만, 그 뒤에 있는 일정은 세션이 아니라 플레이어의 kuid에 저장됩니다. kuid가 없는 세션은 일정을 가질 수 없으므로, 부모가 어떻게 설정하더라도 GET /screentime/get-state{ "enabled": false }를 반환합니다. 플레이어는 신뢰할 수 있는 성인이 동의를 완료한 시점에 kuid를 갖게 되므로, 먼저 그 흐름을 진행하고 이후 세션에서는 저장한 kuid를 다시 사용하세요. 세션 및 권한Challenge를 참조하세요.

부모가 일정을 설정하지 않은 플레이어는 오류가 아닙니다. GET /screentime/get-state{ "enabled": false }를 반환하고, 어떤 판정도 차단하지 않으며 이벤트도 발생하지 않습니다. 이 경우를 전제로 구현하세요. 대부분의 플레이어가 이 상태입니다.

사용 시간 보고

사용 시간을 보고하는 방법은 두 가지이며, 이 선택은 겉모습의 차이가 아닙니다. 제품이 실시간 신호를 받을 수 있는지가 이것으로 결정됩니다.

진행 중인 세션: startend사후 보고: push
엔드포인트/screentime/start, /screentime/end/screentime/push
일일 합계에 반영
Screentime.BreakReminder발생함발생하지 않음
Screentime.LimitWarning발생함발생하지 않음
Screentime.LimitReached발생함발생하지 않음
보고 가능 범위실시간, 해당 시점으로부터 5분 이내지난 7일 이내의 임의 시점
적합한 경우제품이 플레이어의 시작과 중지를 알 수 있는 경우제품이 세션이 끝난 뒤에야 사용 시간을 아는 경우
실시간 이벤트가 필요하면 startend를 선택하세요

이것은 엔드포인트 목록만 보고는 추측할 수 없는 스크린 타임의 핵심입니다. /screentime/push로 보고된 사용 시간은 일일 합계에 더해지고 부모의 Family Connect 차트에도 표시되지만, 휴식 알림도 한도 경고도 한도 도달 이벤트도 발생하지 않습니다.

이유는 시점입니다. k-ID는 /screentime/start를 호출할 때 알림과 경고를 예약하므로 그것들은 플레이어가 아직 세션 중일 때 발생합니다. 90분 세션을 사후에 보고하면 60분 시점의 휴식 알림은 30분 늦게 발생할 수밖에 없고, 그것은 휴식 알림으로 기능하지 않습니다. 제품이 휴식 알림 의무를 지고 있다면 /screentime/push로는 이를 충족할 수 없습니다.

조용한 시간 이벤트와 Screentime.ScheduleChanged는 세션 보고 방식이 아니라 부모의 달력에 의해 구동되므로 어느 방식에서도 발생합니다.

세션 상태 머신

sessionId당 동시에 진행할 수 있는 스크린 타임 세션은 하나뿐이며, 다음 규칙은 여기에서 비롯됩니다.

  • id는 제품에서 생성합니다. /screentime/start에는 새 UUID를 보내고 /screentime/end에는 같은 id를 보내세요. 진행 중인 세션과 일치하지 않는 id로 보낸 endNOT_FOUND를 반환합니다.
  • 재시도는 안전합니다. 같은 idstart를 반복하면 중복 집계 없이 accepted가 반환됩니다. 이미 종료된 세션에 대해 end를 반복해도 accepted가 반환됩니다.
  • 다른 id로 보낸 두 번째 startstatus: "replaced"를 반환합니다. k-ID는 이전 세션을 종료하고 그 시간을 집계한 뒤 새 세션을 진행 중으로 만듭니다. 앱이 강제 종료되거나 기기가 재시작되어 end가 도착하지 않은 경우의 복구 경로가 바로 이것입니다. start를 다시 호출하고 replaced인지 확인하면 됩니다. 같은 플레이어가 두 번째 기기에서 플레이를 시작할 때도 같은 일이 벌어지며, 그 경우에는 부모가 볼 수 있는 결과가 따릅니다. 첫 번째 기기는 집계가 멈추고 알림도 더 이상 받지 못합니다. 사용 시간 집계 방식을 참조하세요.
  • k-ID는 4시간이 지난 세션을 자동으로 종료합니다. 그 시점까지의 시간은 집계되고 대기 중인 알림은 취소됩니다. 이는 end를 아예 보내지 않게 된 제품의 영향 범위를 제한하는 장치입니다. end를 호출하는 것을 대신하는 것이 아니라 안전망입니다.
  • 타임스탬프는 실시간이어야 합니다. /screentime/start는 5분보다 이전의 timestamp와 미래의 timestamp를 거부합니다. /screentime/end는 start의 타임스탬프 이후여야 하며, 약 1분 정도의 앞선 시계 오차를 허용합니다.

startend는 모두 응답에 카운터를 반환합니다. 따라서 세션의 시작이나 끝에 플레이어에게 남은 시간을 표시하기 위해 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" }
]
}

reasoninvalid_input이면 그 구간은 검증 규칙을 위반한 것이므로 그대로 재시도해도 다시 실패합니다. 구간은 이미 완료되어 있어야 하고, timestamp는 지난 7일 이내여야 하며, durationSeconds는 1초 이상 24시간 이하여야 합니다. reasoninternal_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"
}
]
}
}

다음 순서로 읽으세요.

  1. enabled. false일 때 응답에는 다른 내용이 없으며, 이유는 세 가지입니다. 부모가 스크린 타임을 설정하지 않았거나, 부모가 설정했지만 제한을 껐거나, 세션에 kuid가 없어 일정이 속할 플레이어가 없는 경우입니다. 테스트에서 가장 자주 마주치는 것은 세 번째입니다. 세 경우 모두 아무것도 제한하지 마세요.
  2. access.allowed. 이것이 판정이며, 일일 한도와 모든 조용한 시간대가 이미 결합되어 있습니다. schedule에서 직접 다시 계산하지 마세요.
  3. access.details. allowedfalse일 때만 포함됩니다. reasonquiet_hours 또는 limit_reached이고, resumesAt은 제한이 해제되는 시각으로 플레이어에게 알려주면 유용한 정보입니다. 두 제한이 동시에 적용되는 경우에는 더 늦게 끝나는 쪽이 반환됩니다.
  4. state. 제품 UI에 쓸 오늘의 수치입니다. timeUsedTodayMinutes는 진행 중인 세션의 시간을 포함하고, continuousUsageMinutes는 그 세션만의 길이입니다. 오늘 요일에 한도가 설정되어 있지 않으면 timeLimitTodayMinutestimeRemainingTodayMinutes는 포함되지 않습니다.
  5. schedule. 표시용 부모의 규칙입니다. 시각은 schedule.timezone 기준 HH:MM입니다.

이벤트 처리

이 이벤트들은 제품의 웹훅이 해당 이벤트 유형을 구독한 경우에만 도착합니다. 한 번도 실행되지 않는 핸들러를 디버깅하기 전에 시작하기 전에를 확인하세요.

이벤트의미대응
Screentime.BreakReminder플레이어가 설정된 간격 동안 하나의 세션을 계속했습니다휴식을 권하는 안내를 표시합니다
Screentime.LimitWarning오늘 15분 또는 5분이 남았습니다저장 지점에 도달할 수 있도록 플레이어에게 알립니다
Screentime.LimitReached오늘의 한도가 적용되었습니다제한을 적용하고 부모에게 요청할 방법을 제시합니다
Screentime.QuietHoursWarning15분 후에 시간대가 시작됩니다어떤 시간대가 언제 시작되는지 알립니다
Screentime.QuietHoursReached시간대가 시작되었습니다endsAt까지 제한을 적용합니다
Screentime.ScheduleChanged부모가 규칙을 변경했습니다get-state를 다시 가져와 보관 중인 상태를 교체합니다
Screentime.OverrideResult부모가 예외 요청에 답변했습니다페이로드의 state를 적용합니다

전달과 관련해 설계에서 고려할 성질이 세 가지 있습니다.

  • 이벤트는 sessionId를 대상으로 전달됩니다. 일곱 가지 모두에 포함된 data.sessionId로 분기하세요.
  • 전달은 최소 한 번입니다. 같은 이벤트가 두 번 도착할 수 있습니다. 웹훅 개요의 설명대로 핸들러를 멱등하게 만드세요.
  • 조용한 시간 이벤트는 진행 중인 세션을 필요로 하지 않습니다. 제품에 유효한 k-ID 세션이 있는 플레이어에게, 그 시점에 플레이하고 있는지와 무관하게 부모의 달력에 따라 발생합니다. 끼어들 UI가 있다고 가정하지 마세요.

부모에게 추가 시간 요청하기

access.allowedfalse일 때는 막힌 벽만 보여주는 대신 요청할 방법을 플레이어에게 제시하세요.

  1. sessionId와 함께 /screentime/request-override를 호출합니다. 응답에는 idexpiresAt이 담깁니다. id를 저장하세요.
  2. k-ID가 부모에게 알리고, 부모는 Family Connect에서 승인하거나 거부합니다.
  3. 결과는 data.id로 연결되는 Screentime.OverrideResult로 도착하며 statusgranted, denied, expired 중 하나입니다.
  4. 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와 짝을 이루며, statusreplaced인 경우를 오류로 처리하지 않고 다룹니다.
  • 각 경고 쌍에서 두 번째 이벤트만이 아니라 두 이벤트를 모두 처리합니다.
  • 제품의 웹훅이 처리하려는 일곱 가지 Screentime.* 이벤트 유형을 각각 구독하고 있습니다. 웹훅이 선택하지 않은 이벤트 유형은 전달되지 않습니다.
  • 웹훅 핸들러가 멱등하며 서명을 검증합니다.
  • Screentime.ScheduleChanged를 받으면 get-state를 다시 가져옵니다.
  • 한도나 조용한 시간대에 도달한 플레이어에게 /screentime/request-override를 제시하며, expired를 포함한 세 가지 예외 상태를 모두 처리합니다.
  • 제품이 적용하는 제한이 resumesAt 또는 endsAt을 사용해 해제되는 시각을 플레이어에게 알려줍니다.

다음 단계