본문으로 건너뛰기

External Health API 엔드포인트

목차

접근 권한 매트릭스

엔드포인트System AdminIAM AdminService AccountRegular User비고
POST /v1/external-health/requestsLLM/Agent용 요청 생성
POST /v1/external-health/requests/batch일괄 요청 생성
GET /v1/external-health/requests대기 중인 요청 조회
GET /v1/external-health/requests/{requestId}특정 요청 상세 조회
PATCH /v1/external-health/requests/{requestId}/cancel요청 취소
POST /v1/external-health/responses수집 데이터 제출
GET /v1/external-health/data건강 데이터 조회
GET /v1/external-health/data/day-index-range일차 범위별 조회
GET /v1/external-health/data/summary데이터 요약 조회
GET /v1/external-health/platforms플랫폼 연동 목록 조회
POST /v1/external-health/platforms/connect플랫폼 연동
POST /v1/external-health/platforms/disconnect플랫폼 연동 해제
PATCH /v1/external-health/platforms/permissions권한 업데이트
GET /v1/external-health/sleep-data외부 수면 데이터 조회
GET /v1/external-health/admin/users/{userId}/data관리자 데이터 접근
DELETE /v1/external-health/admin/users/{userId}/data사용자 데이터 삭제
GET /v1/external-health/users/me/data-export내 데이터 내보내기
GET /v1/external-health/admin/users/{userId}/data-export특정 사용자 데이터 내보내기

참고:

  • ✓: 접근 가능
  • ✘: 접근 불가
  • Service Account: Multi-Agent Orchestration 도메인의 LLM/Agent가 사용

데이터 수집 요청 API

데이터 수집 요청 API는 LLM/Agent가 개인화된 건강 데이터 수집 요청을 생성하고, 앱에서 대기 중인 요청을 조회하는 기능을 제공합니다.

1.1 데이터 수집 요청 생성 API

  • HTTP 메서드: POST
  • 경로: /v1/external-health/requests
  • Headers:
    • Content-Type: application/json
    • Authorization: Bearer {serviceAccountToken}

요청 (Request)

{
"userId": "550e8400-e29b-41d4-a716-446655440000",
"type": "heartRate",
"startTime": 1702684800000,
"endTime": 1702771200000,
"interval": {
"type": "hour",
"value": 1
},
"requestedBy": "agent_sleep_analyst",
"context": {
"analysisType": "sleep_quality_prediction",
"priority": "NORMAL",
"reason": "심박수 변화 패턴 분석을 위한 데이터 수집"
},
"expiresInHours": 24
}

참고:

  • type: 수집할 데이터 유형 (stepCount, heartRate, heartRateVariability, respiratoryRate, oxygenSaturation, activeEnergyBurned, sleepAnalysis)
  • startTime, endTime: Unix 타임스탬프 (밀리초)
  • interval: 집계 간격 설정
  • requestedBy: 요청자 식별자 (agent_id, system 등)
  • expiresInHours: 만료 시간 (선택, 기본 24시간, 범위 1-168시간)

응답 (Response)

  • 성공 응답 (201 Created)
{
"id": "req_550e8400-e29b-41d4-a716-446655440001",
"userId": "550e8400-e29b-41d4-a716-446655440000",
"dayIndex": 15,
"type": "heartRate",
"startTime": 1702684800000,
"endTime": 1702771200000,
"interval": {
"type": "hour",
"value": 1
},
"status": "PENDING",
"expiresAt": 1702857600000,
"requestedBy": "agent_sleep_analyst",
"context": {
"analysisType": "sleep_quality_prediction",
"priority": "NORMAL",
"reason": "심박수 변화 패턴 분석을 위한 데이터 수집"
},
"timezoneId": "Europe/Berlin",
"timezoneOffset": 60,
"createdAt": 1702771200000,
"updatedAt": 1702771200000
}
  • 오류 응답 (400 Bad Request)
{
"code": 21001,
"message": "INVALID_DATA_TYPE",
"detail": "지원하지 않는 데이터 유형입니다."
}

1.2 데이터 수집 요청 일괄 생성 API

  • HTTP 메서드: POST
  • 경로: /v1/external-health/requests/batch
  • Headers:
    • Content-Type: application/json
    • Authorization: Bearer {serviceAccountToken}

요청 (Request)

{
"userId": "550e8400-e29b-41d4-a716-446655440000",
"requests": [
{
"type": "heartRate",
"startTime": 1702684800000,
"endTime": 1702771200000,
"interval": { "type": "hour", "value": 1 }
},
{
"type": "stepCount",
"startTime": 1702684800000,
"endTime": 1702771200000,
"interval": { "type": "day", "value": 1 }
},
{
"type": "sleepAnalysis",
"startTime": 1702684800000,
"endTime": 1702771200000,
"interval": { "type": "day", "value": 1 }
}
],
"requestedBy": "agent_sleep_analyst"
}

응답 (Response)

  • 성공 응답 (201 Created)
{
"requests": [
{
"id": "req_001",
"type": "heartRate",
"status": "PENDING"
},
{
"id": "req_002",
"type": "stepCount",
"status": "PENDING"
},
{
"id": "req_003",
"type": "sleepAnalysis",
"status": "PENDING"
}
],
"totalCreated": 3
}

1.3 대기 중인 요청 조회 API (앱용)

  • HTTP 메서드: GET
  • 경로: /v1/external-health/requests
  • Headers:
    • Authorization: Bearer {accessToken}
  • 쿼리 파라미터:
    • platform: 플랫폼 지정 (iOS 또는 Android, 필수)
    • filterByGrantedPermissions: 승인된 권한 기준 필터링 여부 (선택, 기본 false)

참고: 서버는 요청 조회 시 사용자의 플랫폼 연동 상태 및 권한을 검증합니다. (요구사항 EXH-FR-BE-005-1)

응답 (Response)

  • 성공 응답 (200 OK)
{
"requests": [
{
"id": "B437BCAC-CFFF-468C-9A30-5786F74BDB87",
"type": "stepCount",
"startTime": 1764550800000,
"endTime": 1764637200000,
"interval": {
"type": "day",
"value": 1
}
},
{
"id": "BEC32F44-2647-44C8-8C74-1DF64097FA31",
"type": "heartRate",
"startTime": 1764550800000,
"endTime": 1764637200000,
"interval": {
"type": "hour",
"value": 1
}
},
{
"id": "6406D4AE-9C55-40D3-BE25-5EE0AD3859E9",
"type": "sleepAnalysis",
"startTime": 1764550800000,
"endTime": 1764637200000
}
]
}

응답 필드 명세

FieldData TypeRequiredComment
requestsArray<Object>YES최소한 empty array
request.idUUIDYES요청의 고유 식별자
request.typeString (Enum)YESstepCount: quantity, heartRate: quantity, sleepAnalysis: category
request.startTimeIntYESUnix Epoch Time (milliseconds)
request.endTimeIntYESUnix Epoch Time (milliseconds)
request.intervalObjectNO없을 경우 RawData 반환
request.interval.typeString (Enum)Conditionalyear, month, day, hour, minute, second
request.interval.valueIntConditional집계 간격 값
  • 오류 응답 (403 Forbidden - 권한 없음)
{
"code": 21022,
"message": "PERMISSION_NOT_GRANTED",
"detail": "요청된 데이터 유형에 대한 권한이 승인되지 않았습니다.",
"requiredPermissions": ["heartRate", "sleepAnalysis"],
"grantedPermissions": ["stepCount"]
}

참고:

  • filterByGrantedPermissions=true 파라미터를 사용하면, 권한이 없는 데이터 유형의 요청은 오류 대신 응답에서 제외됩니다.
  • 앱에서는 이 응답을 받아 각 요청에 해당하는 데이터를 HealthKit/Health Connect SDK를 통해 수집합니다.

1.4 요청 취소 API

  • HTTP 메서드: PATCH
  • 경로: /v1/external-health/requests/{requestId}/cancel
  • Headers:
    • Content-Type: application/json
    • Authorization: Bearer {serviceAccountToken}

요청 (Request)

{
"reason": "분석 취소로 인한 요청 취소"
}

응답 (Response)

  • 성공 응답 (200 OK)
{
"id": "req_550e8400-e29b-41d4-a716-446655440001",
"status": "CANCELLED",
"cancelledAt": 1702771500000,
"cancelReason": "분석 취소로 인한 요청 취소"
}

데이터 수집 응답 API

데이터 수집 응답 API는 앱에서 수집한 건강 데이터를 서버로 제출하는 기능을 제공합니다.

2.1 수집 데이터 제출 API

  • HTTP 메서드: POST
  • 경로: /v1/external-health/responses
  • Headers:
    • Content-Type: application/json
    • Authorization: Bearer {accessToken}

요청 (Request)

{
"responses": [
{
"requestId": "B437BCAC-CFFF-468C-9A30-5786F74BDB87",
"os": "iOS",
"type": "stepCount",
"startTime": "2025-12-01T10:00:00.000+09:00",
"endTime": "2025-12-02T10:00:00.000+09:00",
"interval": {
"type": "day",
"value": 1
},
"extractedAt": "2025-12-03T07:00:00.000+09:00",
"records": [
{
"startTime": "2025-12-01T10:00:00.000+09:00",
"endTime": "2025-12-02T10:00:00.000+09:00",
"sum": 100,
"unit": "count"
}
]
},
{
"requestId": "BEC32F44-2647-44C8-8C74-1DF64097FA31",
"os": "iOS",
"type": "heartRate",
"startTime": "2025-12-01T10:00:00.000+09:00",
"endTime": "2025-12-02T10:00:00.000+09:00",
"interval": {
"type": "hour",
"value": 1
},
"extractedAt": "2025-12-03T07:00:00.000+09:00",
"records": [
{
"startTime": "2025-12-01T10:00:00.000+09:00",
"endTime": "2025-12-01T11:00:00.000+09:00",
"average": 110.123,
"unit": "count/min"
},
{
"startTime": "2025-12-01T11:00:00.000+09:00",
"endTime": "2025-12-01T12:00:00.000+09:00",
"average": 123.456,
"unit": "count/min"
}
]
},
{
"requestId": "6406D4AE-9C55-40D3-BE25-5EE0AD3859E9",
"os": "iOS",
"type": "sleepAnalysis",
"startTime": "2025-12-01T10:00:00.000+09:00",
"endTime": "2025-12-02T10:00:00.000+09:00",
"extractedAt": "2025-12-03T07:00:00.000+09:00",
"records": [
{
"startTime": "2025-12-01T23:00:00.000+09:00",
"endTime": "2025-12-01T23:05:00.000+09:00",
"category": "inBed"
},
{
"startTime": "2025-12-01T23:05:00.000+09:00",
"endTime": "2025-12-01T23:10:00.000+09:00",
"category": "asleepCore"
}
]
}
]
}

요청 필드 명세

FieldData TypeRequiredComment
responsesArray<Object>YES최소한 empty array
response.requestIdUUIDYESGET 요청으로 조회한 요청 ID
response.osStringYESiOS 또는 Android
response.typeString (Enum)YESstepCount, heartRate, sleepAnalysis 등
response.startTimeStringYESISO8601(ms) + TimeZone
response.endTimeStringYESISO8601(ms) + TimeZone
response.intervalObjectNO집계 간격 설정
response.interval.typeString (Enum)Conditionalyear, month, day, hour, minute, second
response.interval.valueIntConditional집계 간격 값
response.extractedAtStringYESISO8601(ms) + TimeZone
response.recordsArray<Object>YES최소한 empty array
response.record.startTimeStringYESISO8601(ms) + TimeZone
response.record.endTimeStringYESISO8601(ms) + TimeZone
response.record.sumIntNOstepCount 등 합계 데이터용
response.record.averageDoubleNOheartRate 등 평균 데이터용
response.record.categoryStringNOsleepAnalysis 카테고리 (inBed, asleepCore, asleepDeep, asleepREM, awake)
response.record.unitStringNO단위 (count, count/min 등)

응답 (Response)

  • 성공 응답 (200 OK)
{
"results": [
{
"requestId": "B437BCAC-CFFF-468C-9A30-5786F74BDB87",
"status": "COLLECTED",
"processedRecords": 1
},
{
"requestId": "BEC32F44-2647-44C8-8C74-1DF64097FA31",
"status": "COLLECTED",
"processedRecords": 2
},
{
"requestId": "6406D4AE-9C55-40D3-BE25-5EE0AD3859E9",
"status": "COLLECTED",
"processedRecords": 2
}
]
}
  • 오류 응답 (400 Bad Request - 유효하지 않은 요청 ID)
{
"code": 21010,
"message": "INVALID_REQUEST_ID",
"detail": "존재하지 않거나 이미 처리된 요청 ID입니다.",
"requestId": "req_invalid"
}
  • 오류 응답 (400 Bad Request - 데이터 검증 실패)
{
"code": 21011,
"message": "DATA_VALIDATION_FAILED",
"detail": "수집된 데이터의 유효성 검증에 실패했습니다.",
"validationResult": {
"isValid": false,
"validRecordCount": 20,
"invalidRecordCount": 4,
"errors": [
{
"recordIndex": 5,
"field": "value",
"message": "심박수 값이 유효 범위(30-250)를 벗어났습니다.",
"code": "OUT_OF_RANGE"
}
],
"warnings": []
}
}
  • 오류 응답 (409 Conflict - 중복 제출)
{
"code": 21012,
"message": "DUPLICATE_RESPONSE",
"detail": "이미 처리된 요청에 대한 중복 응답입니다.",
"requestId": "req_550e8400-e29b-41d4-a716-446655440001"
}

건강 데이터 조회 API

3.1 건강 데이터 조회 API (기간별)

  • HTTP 메서드: GET
  • 경로: /v1/external-health/data
  • 쿼리 파라미터:
    • startDate: 시작일 (YYYY-MM-DD)
    • endDate: 종료일 (YYYY-MM-DD)
    • types: 데이터 유형 목록 (쉼표 구분, 선택)
  • Headers:
    • Authorization: Bearer {accessToken}

응답 (Response)

  • 성공 응답 (200 OK)
{
"data": [
{
"id": "data_001",
"type": "heartRate",
"dayIndex": 15,
"timestamp": 1702684800000,
"value": 72,
"unit": "bpm",
"verboseValue": "72 bpm",
"aggregationType": "average",
"qualityStatus": "VALID",
"source": "HealthKit",
"createdAt": 1702771200000
},
{
"id": "data_002",
"type": "stepCount",
"dayIndex": 15,
"timestamp": 1702684800000,
"value": 8532,
"unit": "steps",
"verboseValue": "8,532 걸음",
"aggregationType": "sum",
"qualityStatus": "VALID",
"source": "HealthKit",
"createdAt": 1702771200000
}
],
"pagination": {
"total": 50,
"page": 1,
"limit": 20
}
}

3.2 건강 데이터 조회 API (일차 범위별)

  • HTTP 메서드: GET
  • 경로: /v1/external-health/data/day-index-range
  • 쿼리 파라미터:
    • startDayIndex: 시작 일차
    • endDayIndex: 종료 일차
    • types: 데이터 유형 목록 (쉼표 구분, 선택)
  • Headers:
    • Authorization: Bearer {accessToken}

응답 (Response)

  • 성공 응답 (200 OK)
{
"data": [
{
"dayIndex": 15,
"date": "2025-12-01",
"records": [
{
"type": "heartRate",
"averageValue": 72,
"minValue": 58,
"maxValue": 95,
"unit": "bpm",
"recordCount": 24
},
{
"type": "stepCount",
"totalValue": 8532,
"unit": "steps",
"recordCount": 1
}
]
}
]
}

3.3 건강 데이터 요약 조회 API

  • HTTP 메서드: GET
  • 경로: /v1/external-health/data/summary
  • 쿼리 파라미터:
    • startDate: 시작일 (YYYY-MM-DD)
    • endDate: 종료일 (YYYY-MM-DD)
  • Headers:
    • Authorization: Bearer {accessToken}

응답 (Response)

  • 성공 응답 (200 OK)
{
"userId": "550e8400-e29b-41d4-a716-446655440000",
"startDate": "2025-12-01",
"endDate": "2025-12-07",
"dayIndexRange": {
"start": 15,
"end": 21
},
"summaries": {
"heartRate": {
"totalRecords": 168,
"validRecords": 165,
"average": 72.5,
"min": 55,
"max": 110,
"unit": "bpm"
},
"stepCount": {
"totalRecords": 7,
"validRecords": 7,
"sum": 52340,
"average": 7477,
"min": 4230,
"max": 12500,
"unit": "steps"
}
},
"sleepSummary": {
"totalNights": 7,
"avgSleepDuration": 420,
"avgSleepEfficiency": 0.88,
"avgRemPercentage": 22.5,
"avgDeepPercentage": 18.3
}
}

플랫폼 연동 API

4.1 플랫폼 연동 목록 조회 API

  • HTTP 메서드: GET
  • 경로: /v1/external-health/platforms
  • Headers:
    • Authorization: Bearer {accessToken}

응답 (Response)

  • 성공 응답 (200 OK)
{
"connections": [
{
"id": "conn_001",
"platform": "iOS",
"status": "CONNECTED",
"permissions": {
"dataTypes": ["heartRate", "stepCount", "sleepAnalysis", "heartRateVariability"],
"lastUpdatedAt": 1702684800000
},
"lastSyncAt": 1702771200000,
"lastSyncStatus": "SUCCESS",
"connectedAt": 1700000000000
}
]
}

4.2 플랫폼 연동 API

  • HTTP 메서드: POST
  • 경로: /v1/external-health/platforms/connect
  • Headers:
    • Content-Type: application/json
    • Authorization: Bearer {accessToken}

요청 (Request)

{
"platform": "iOS",
"permissions": ["heartRate", "stepCount", "sleepAnalysis", "heartRateVariability"],
"deviceId": "device_abc123",
"deviceModel": "iPhone 15 Pro",
"osVersion": "17.2"
}

응답 (Response)

  • 성공 응답 (201 Created)
{
"id": "conn_001",
"platform": "iOS",
"status": "CONNECTED",
"permissions": {
"dataTypes": ["heartRate", "stepCount", "sleepAnalysis", "heartRateVariability"],
"lastUpdatedAt": 1702771200000
},
"connectedAt": 1702771200000
}

4.3 플랫폼 연동 해제 API

  • HTTP 메서드: POST
  • 경로: /v1/external-health/platforms/disconnect
  • Headers:
    • Content-Type: application/json
    • Authorization: Bearer {accessToken}

요청 (Request)

{
"platform": "iOS"
}

응답 (Response)

  • 성공 응답 (200 OK)
{
"id": "conn_001",
"platform": "iOS",
"status": "DISCONNECTED",
"disconnectedAt": 1702771500000
}

4.4 플랫폼 권한 업데이트 API

  • HTTP 메서드: PATCH
  • 경로: /v1/external-health/platforms/permissions
  • Headers:
    • Content-Type: application/json
    • Authorization: Bearer {accessToken}

요청 (Request)

{
"platform": "iOS",
"permissions": ["heartRate", "stepCount", "sleepAnalysis"]
}

응답 (Response)

  • 성공 응답 (200 OK)
{
"id": "conn_001",
"platform": "iOS",
"permissions": {
"dataTypes": ["heartRate", "stepCount", "sleepAnalysis"],
"lastUpdatedAt": 1702771600000
}
}

외부 수면 데이터 API

5.1 외부 수면 데이터 조회 API

  • HTTP 메서드: GET
  • 경로: /v1/external-health/sleep-data
  • 쿼리 파라미터:
    • startDate: 시작일 (YYYY-MM-DD)
    • endDate: 종료일 (YYYY-MM-DD)
  • Headers:
    • Authorization: Bearer {accessToken}

응답 (Response)

  • 성공 응답 (200 OK)
{
"sleepData": [
{
"id": "sleep_001",
"dayIndex": 15,
"sleepStart": 1702677600000,
"sleepEnd": 1702702800000,
"totalSleepMinutes": 420,
"sleepEfficiency": 0.88,
"timeInBed": 477,
"awakeTime": 57,
"sleepStages": [
{
"stage": "LIGHT",
"startTime": 1702677600000,
"endTime": 1702682400000,
"durationMinutes": 80
},
{
"stage": "DEEP",
"startTime": 1702682400000,
"endTime": 1702689600000,
"durationMinutes": 120
},
{
"stage": "REM",
"startTime": 1702689600000,
"endTime": 1702696800000,
"durationMinutes": 120
},
{
"stage": "LIGHT",
"startTime": 1702696800000,
"endTime": 1702702800000,
"durationMinutes": 100
}
],
"remSleepMinutes": 120,
"lightSleepMinutes": 180,
"deepSleepMinutes": 120,
"qualityStatus": "VALID",
"source": "HealthKit",
"createdAt": 1702771200000
}
]
}

데이터 관리 API

6.1 사용자 데이터 내보내기 API

  • HTTP 메서드: GET
  • 경로: /v1/external-health/users/me/data-export
  • 쿼리 파라미터:
    • format: 내보내기 형식 (JSON 또는 CSV, 기본 JSON)
  • Headers:
    • Authorization: Bearer {accessToken}

응답 (Response)

  • 성공 응답 (200 OK)
{
"exportId": "export_001",
"status": "PROCESSING",
"format": "JSON",
"requestedAt": 1702771200000,
"estimatedCompletionTime": 1702771500000
}

참고: 대용량 데이터의 경우 비동기 처리되며, 완료 시 다운로드 URL이 제공됩니다.

6.2 관리자 - 사용자 데이터 조회 API

  • HTTP 메서드: GET
  • 경로: /v1/external-health/admin/users/{userId}/data
  • 쿼리 파라미터:
    • startDate: 시작일 (YYYY-MM-DD)
    • endDate: 종료일 (YYYY-MM-DD)
    • types: 데이터 유형 목록 (쉼표 구분, 선택)
  • Headers:
    • Authorization: Bearer {adminToken}

응답 (Response)

  • 성공 응답 (200 OK)
{
"userId": "550e8400-e29b-41d4-a716-446655440000",
"data": [
{
"type": "heartRate",
"records": [...]
}
],
"accessedAt": 1702771200000,
"accessedBy": "admin_001"
}

6.3 관리자 - 사용자 데이터 삭제 API

  • HTTP 메서드: DELETE
  • 경로: /v1/external-health/admin/users/{userId}/data
  • Headers:
    • Authorization: Bearer {adminToken}

응답 (Response)

  • 성공 응답 (200 OK)
{
"userId": "550e8400-e29b-41d4-a716-446655440000",
"deletedRecords": {
"healthData": 1250,
"sleepData": 45,
"requests": 120,
"responses": 120,
"platformConnections": 1
},
"deletedAt": 1702771200000,
"deletedBy": "admin_001"
}

오류 코드

참고: External Health 도메인은 21000-21099 범위의 에러 코드를 사용합니다. 전체 도메인 에러 코드 맵은 error-code-registry.md를 참조하세요.

코드메시지설명
21000SERVER_ERROR서버 내부 오류
21001INVALID_DATA_TYPE지원하지 않는 데이터 유형
21002INVALID_INTERVAL유효하지 않은 집계 간격
21003INVALID_TIME_RANGE유효하지 않은 시간 범위
21004USER_NOT_FOUND사용자를 찾을 수 없음
21010INVALID_REQUEST_ID유효하지 않은 요청 ID
21011DATA_VALIDATION_FAILED데이터 유효성 검증 실패
21012DUPLICATE_RESPONSE중복 응답 제출
21013REQUEST_EXPIRED만료된 요청에 대한 응답
21014REQUEST_ALREADY_PROCESSED이미 처리된 요청
21021PLATFORM_ALREADY_CONNECTED플랫폼이 이미 연동됨
21022PERMISSION_NOT_GRANTED권한이 승인되지 않음
21030DATA_NOT_FOUND데이터를 찾을 수 없음
21031DATA_OUT_OF_RANGE데이터가 유효 범위를 벗어남
21032DATA_ANOMALY_DETECTED데이터 이상치 감지
21040EXPORT_IN_PROGRESS데이터 내보내기가 이미 진행 중
21041EXPORT_FAILED데이터 내보내기 실패
21050UNAUTHORIZED_ACCESS권한 없는 접근
21051ADMIN_ACCESS_REQUIRED관리자 권한 필요

변경 이력

버전날짜작성자변경 내용
0.1.02025-12-15claude@weltcorp.com최초 작성