CDK

스크린 타임

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

스크린 타임이 무엇이고 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 }를 반환하고, 어떤 판정도 차단하지 않으며 이벤트도 발생하지 않습니다. 이 경우를 전제로 구현하세요. 대부분의 플레이어가 이 상태입니다.

사용 시간 보고

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

진행 중인 세션: 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"
      }
    ]
  }
}

다음 순서로 읽으세요.

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

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

다음 단계

On this page