Business Object 및 노드 검색
RealityConnect API는 범위가 다른 두 가지 검색 경로를 제공합니다. business object 검색은 트윈 내부에서 POI, 존, 애셋 및 해당 메타데이터를 찾고, 노드 검색은 조직의 노드 계층 구조(사이트, 폴더, 트윈, 파일)를 검색합니다. 두 경로 모두 실험적이며 변경될 수 있습니다.
엔드포인트
섹션 제목: “엔드포인트”| 메서드 | 경로 | 스코프 | 목적 |
|---|---|---|---|
GET | /v1/twin/{contextId}/search | Basic | 트윈 내부의 business object 및 메타데이터 검색 |
GET | /v1/nodes/search | ReadHierarchy | 조직의 노드 계층 구조 검색 |
Business object 검색
섹션 제목: “Business object 검색”GET /v1/twin/{contextId}/search는 하나의 트윈(또는 contextId로 식별되는 사이트/드래프트) 내부의 POI, 존, box asset, 측정값 및 기타 business object를 검색하며, 이름, 예약된 필드, 애셋 메타데이터, 객체별 속성을 기준으로 일치합니다.
쿼리 매개변수
섹션 제목: “쿼리 매개변수”| 매개변수 | 타입 | 참고 |
|---|---|---|
fieldFilters | JSON 객체 배열 | 예약된 키 필터. 아래 참조. |
metadataFilters | JSON 객체 배열 | 트윈 애셋 메타데이터 속성과 일치. 아래 참조. |
additionalPropertyFilters | JSON 객체 배열 | 객체 유형별 속성과 일치(예: POI의 icon). 아래 참조. |
page | 정수 | 양수, 기본값 1. |
limit | 정수 | 1–50, 기본값 50. |
searchNameQuery | string | fieldFilters의 이름 필터와 독립적인 전용 이름 필터. |
nameMatch | exact | contains | searchNameQuery의 매치 모드. 기본값 contains. |
createdById | uuid | 생성자로 필터링. |
createdBefore / createdAfter | timestamp | 엄격하게 이전/이후. 형식 YYYY-MM-DDTHH:mm:ss(시간대 오프셋 없음, 밀리초 없음). |
updatedBefore / updatedAfter | timestamp | 동일한 형식. |
sortBy | name | createdAt | updatedAt | 정렬 필드. |
sortDir | ASC | DESC | 정렬 방향. |
fieldFilters, metadataFilters, additionalPropertyFilters는 각각 필터당 하나의 JSON 객체 문자열로 전송되며, 여러 필터를 지정하려면 쿼리 매개변수를 반복합니다.
?fieldFilters={"key":"type","values":["poi"]}&fieldFilters={"key":"type","values":["zone"]}각 필터 객체의 형태는 { "key": string, "values": string[] }입니다. 한 필터의 values 배열에 있는 여러 값은 OR로 결합됩니다.
**fieldFilters**는 다음 예약된 키만 허용하며, 그 외의 키는 조용히 무시됩니다.
| 키 | 일치 대상 |
|---|---|
type | Business object 유형. enum 값(Poi, Zone, BoxAsset, CubePrimitive, PlanePrimitive, DistanceMeasure, CoordinateMeasure, DiameterMeasure, SurfaceMeasure, OrthogonalMeasure, Cut, ModelAsset) 또는 정규화된 kebab-case 토큰(PascalCase 이름을 하이픈으로 연결하고 소문자로 변환, 예: BoxAsset → box-asset) 중 하나를 사용할 수 있습니다. |
asset-type | 객체의 애셋 유형(assetTypeId에 매핑). 정확히 일치. |
name | 이름에 대한 접두사 매칭만 수행, 퍼지 없음. |
id | 정확한 객체 id 일치. |
fieldFilters 값은 항상 일반 문자열 또는 enum 토큰이며, 아래의 숫자/범위 구문은 적용되지 않습니다.
**metadataFilters와 additionalPropertyFilters**는 키를 기준으로 스키마에 정의된 메타데이터 속성(metadataFilters) 또는 공통 메타데이터에 속하지 않는 객체 유형별 속성(additionalPropertyFilters, 예: POI의 icon)을 대상으로 합니다. values의 각 값은 다음을 지원합니다.
| 값 구문 | 의미 |
|---|---|
| 일반 문자열 | 정확히 일치. values에 있는 여러 일반 문자열은 OR로 결합됩니다. |
* 또는 빈 문자열 | 건너뜁니다 — 유일한 값인 경우 필터는 키 존재 여부만으로 일치합니다. |
=123, !=123, >123, >=123, <123, <=123 | 숫자 비교. 정수, 소수, 지수 표기법(예: 1.5e+35)을 지원합니다. |
range:[1..10] | 포함 범위(양 끝 포함). |
range:(1..10) | 배제 범위(양 끝 미포함). |
range:[1..10) / range:(1..10] | 포함/배제가 혼합된 경계. |
true / false | additionalPropertyFilters 전용 — 불리언 속성과 리터럴로 일치. |
필터 결합
섹션 제목: “필터 결합”fieldFilters, metadataFilters, additionalPropertyFilters는 AND 방식으로 결합됩니다. 결과가 되려면 모든 필터를 만족해야 합니다. 하나의 필터 안에서는 values 배열의 값들이 OR로 결합되므로, key가 같은 두 개의 fieldFilters 객체는 “둘 중 하나라도 일치”로 동작하는 반면, 서로 다른 키(또는 서로 다른 필터 배열)에 대한 필터는 결과 집합을 계속 좁혀 나갑니다.
유형 필터와 숫자 메타데이터 범위를 결합한 예(검사 압력이 80~120 사이로 기록된 POI):
GET {api_url}/v1/twin/{contextId}/search?fieldFilters=%7B%22key%22%3A%22type%22%2C%22values%22%3A%5B%22poi%22%5D%7D&metadataFilters=%7B%22key%22%3A%22inspection.pressure%22%2C%22values%22%3A%5B%22range%3A%5B80..120%5D%22%5D%7DAuthorization: Bearer {access_token}애셋 유형과 이름 필터를 결합해, 최근 업데이트 순으로 정렬:
GET {api_url}/v1/twin/{contextId}/search?fieldFilters=%7B%22key%22%3A%22asset-type%22%2C%22values%22%3A%5B%22pump%22%5D%7D&fieldFilters=%7B%22key%22%3A%22name%22%2C%22values%22%3A%5B%22vibration%22%5D%7D&sortBy=updatedAt&sortDir=DESCAuthorization: Bearer {access_token}즉, 애셋 유형이 pump이고 동시에 이름이 “vibration”으로 시작하는 객체를 최근 업데이트된 순으로 정렬한 결과입니다.
같은 키에 대한 두 개의 fieldFilters(OR로 결합)와 다른 키에 대한 fieldFilters(AND로 결합)를 함께 사용한 예 — pump 애셋 유형으로 제한된 POI 또는 존:
GET {api_url}/v1/twin/{contextId}/search?fieldFilters=%7B%22key%22%3A%22type%22%2C%22values%22%3A%5B%22poi%22%2C%22zone%22%5D%7D&fieldFilters=%7B%22key%22%3A%22asset-type%22%2C%22values%22%3A%5B%22pump%22%5D%7DAuthorization: Bearer {access_token}불리언 additionalPropertyFilters 리터럴과 문자열 metadataFilters 매치를 결합한 예 — 아이콘 관련 메타데이터가 “warning” 상태를 기록한 활성 POI:
GET {api_url}/v1/twin/{contextId}/search?fieldFilters=%7B%22key%22%3A%22type%22%2C%22values%22%3A%5B%22poi%22%5D%7D&additionalPropertyFilters=%7B%22key%22%3A%22isActive%22%2C%22values%22%3A%5B%22true%22%5D%7D&metadataFilters=%7B%22key%22%3A%22inspection.status%22%2C%22values%22%3A%5B%22warning%22%5D%7DAuthorization: Bearer {access_token}세 가지 필터 입력, 전용 이름 필터, 날짜 범위, 페이지네이션, 정렬을 모두 결합한 요청 — 전체 요청 형태를 보여줍니다:
GET {api_url}/v1/twin/{contextId}/search?fieldFilters=%7B%22key%22%3A%22type%22%2C%22values%22%3A%5B%22poi%22%2C%22zone%22%5D%7D&metadataFilters=%7B%22key%22%3A%22inspection.pressure%22%2C%22values%22%3A%5B%22range%3A%5B80..120%5D%22%5D%7D&additionalPropertyFilters=%7B%22key%22%3A%22isActive%22%2C%22values%22%3A%5B%22true%22%5D%7D&searchNameQuery=Pump&nameMatch=contains&updatedAfter=2026-01-01T00%3A00%3A00&updatedBefore=2026-12-31T23%3A59%3A59&page=1&limit=25&sortBy=updatedAt&sortDir=DESCAuthorization: Bearer {access_token}sortBy | 정렬 기준 |
|---|---|
name | 객체 이름 |
createdAt | 생성 타임스탬프 |
updatedAt | 최종 업데이트 타임스탬프 |
각 sortBy 값은 어떤 sortDir 값과도 결합할 수 있습니다:
GET {api_url}/v1/twin/{contextId}/search?fieldFilters=%7B%22key%22%3A%22type%22%2C%22values%22%3A%5B%22poi%22%5D%7D&sortBy=name&sortDir=ASCAuthorization: Bearer {access_token}GET {api_url}/v1/twin/{contextId}/search?fieldFilters=%7B%22key%22%3A%22type%22%2C%22values%22%3A%5B%22poi%22%5D%7D&sortBy=createdAt&sortDir=DESCAuthorization: Bearer {access_token}GET {api_url}/v1/twin/{contextId}/search?fieldFilters=%7B%22key%22%3A%22type%22%2C%22values%22%3A%5B%22poi%22%5D%7D&sortBy=updatedAt&sortDir=DESCAuthorization: Bearer {access_token}sortDir는 생략 시 기본값이 ASC입니다. sortBy는 문서화된 기본 필드가 없습니다 — 생략하면 내부 관련성 점수(내림차순)를 기준으로 정렬되며, 동점일 경우 id(오름차순)로 구분됩니다. 이 점수에 영향을 미치는 것은 key: "name"을 가진 fieldFilters뿐이며, 그 외의 모든 필터는 순수한 예/아니오 일치일 뿐 순위에는 영향을 주지 않으므로, 실제로는 name 필터 없이 sortBy를 생략하면 결과가 id 순서로 반환됩니다. 이는 보장된 계약이 아니라 구현 세부 사항이므로, 통합 환경에서 특정 필드 순서가 중요하다면 항상 sortBy를 명시적으로 지정하세요.
숫자 및 범위 필터
섹션 제목: “숫자 및 범위 필터”metadataFilters와 additionalPropertyFilters 값 안에서는 리터럴 매칭 대신 비교 연산자나 범위를 사용할 수 있습니다. 이 구문은 fieldFilters에는 적용되지 않습니다 — fieldFilters(type, asset-type, name, id)는 항상 리터럴/enum 매칭입니다.
| 연산자 | 예시 | 의미 |
|---|---|---|
= | =123 | 같음 |
!= | !=123 | 같지 않음 |
> | >123 | 초과 |
>= | >=123 | 이상 |
< | <123 | 미만 |
<= | <=123 | 이하 |
range:[a..b] | range:[80..120] | 양 끝 포함 |
range:(a..b) | range:(80..120) | 양 끝 배제 |
range:[a..b) | range:[80..120) | 하한 포함, 상한 배제 |
range:(a..b] | range:(80..120] | 하한 배제, 상한 포함 |
정수, 소수, 음수, 지수 표기법이 모두 지원됩니다. 예: >=-40, =3.14, range:[1.5e+35..2.0e+35].
이름 필터 vs. 이름 쿼리
섹션 제목: “이름 필터 vs. 이름 쿼리”이름을 일치시키는 방법에는 두 가지가 있으며, 각각 동작이 다릅니다.
| 매개변수 | 매치 방식 | 다른 필터와 결합 가능? |
|---|---|---|
fieldFilters={"key":"name",...} | 접두사 매칭, 퍼지 없음 | 예 — 항상 AND로 결합됩니다 |
searchNameQuery + nameMatch | contains(기본값) 또는 exact, 퍼지 없음 | 예 — 항상 AND로 결합됩니다 |
type이나 asset-type과 동일한 AND/OR 규칙 아래에서 결합되는 이름 전용 접두사 매칭이 필요하면 key: "name"을 가진 fieldFilters를 사용하세요. 퍼지 허용 없이 정확한 부분 문자열 또는 완전 일치 이름 조회가 필요하면 — 예를 들어 이름이 그대로 존재하는지 검증할 때 — nameMatch와 함께 searchNameQuery를 사용하세요.
{ "results": [ { "id": "5e1c0b3a-2d9a-4b7d-8f0b-9a2c8f1b7a10", "name": "Fire extinguisher 12", "type": "Poi", "matchedFields": [ { "matchKey": "name", "highlight": "Fire <em>extinguisher</em> 12" } ], "createdAt": "2026-06-01T10:00:00.000Z", "updatedAt": "2026-06-01T10:00:00.000Z", "createdById": "8f0b9a2c-8f1b-4a10-9e1c-0b3a2d9a4b7d" } ], "count": 1}matchedFields는 일치한 필드 또는 메타데이터 속성을 보여주며, 일치한 텍스트는 <em>으로 감싸져 있습니다. matchKey는 루트 필드 이름(예: name)이거나, 일치한 중첩 메타데이터/속성 값에 대해 @<metadata-path> 형태입니다.
하이라이팅 세부 정보
섹션 제목: “하이라이팅 세부 정보”하나의 결과는 여러 개의 matchedFields 항목을 가질 수 있으며, 일치한 필드나 메타데이터 속성마다 하나씩 생성됩니다. 예를 들어, 객체의 이름과 검사 메타데이터 값 모두와 일치하는 fieldFilters 이름 매치와 metadataFilters를 결합한 요청은 다음을 반환합니다:
{ "id": "5e1c0b3a-2d9a-4b7d-8f0b-9a2c8f1b7a10", "name": "Pressure gauge 04", "type": "Poi", "matchedFields": [ { "matchKey": "name", "highlight": "<em>Pressure</em> gauge 04" }, { "matchKey": "@inspection.status", "highlight": "<em>Warning</em>: recalibration due" } ], "createdAt": "2026-06-01T10:00:00.000Z", "updatedAt": "2026-06-01T10:00:00.000Z", "createdById": "8f0b9a2c-8f1b-4a10-9e1c-0b3a2d9a4b7d"}하이라이트 조각은 <em> 마커가 삽입되기 전에 HTML 이스케이프 처리되므로, 일치한 텍스트에 포함된 <, >, &는 원시 마크업이 아니라 <, >, &로 전달됩니다 — 두 번째 이스케이프 과정 없이 HTML에 바로 렌더링해도 안전하지만, 순수 텍스트 값이 필요하면 먼저 디코딩하세요.
엣지 케이스
섹션 제목: “엣지 케이스”| 상황 | 동작 |
|---|---|
| 필터가 전혀 없음(또는 모두 비어 있음) | 순위 없이(match_all) 트윈 내 모든 business object를 limit까지 반환합니다. |
인식되지 않는 fieldFilters 키 | 조용히 무시됩니다 — 해당 필터는 쿼리에 아무 영향을 주지 않으며, 400을 발생시키지 않습니다. |
* 또는 "" 값을 가진 metadataFilters/additionalPropertyFilters | 건너뜁니다. 해당 필터의 values 배열에 있는 모든 값이 이런 식으로 건너뛰어지면, 필터는 여전히 객체가 해당 키에 속성을 가지고 있을 것을 요구합니다 — 값 제약이 없는 키 존재 여부 검사가 됩니다. |
이름 접두사 검색:
GET {api_url}/v1/twin/{contextId}/search?fieldFilters=%7B%22key%22%3A%22name%22%2C%22values%22%3A%5B%22extinguisher%22%5D%7DAuthorization: Bearer {access_token}이름 포함 검색 후 이름순 정렬:
GET {api_url}/v1/twin/{contextId}/search?searchNameQuery=Pump&nameMatch=contains&sortBy=name&sortDir=ASCAuthorization: Bearer {access_token}오류 사례
섹션 제목: “오류 사례”| 상태 | 원인 |
|---|---|
400 Bad Request | fieldFilters, metadataFilters, additionalPropertyFilters 값 중 하나가 유효한 JSON이 아니거나 { key, values } 형태와 일치하지 않습니다. |
전체 예제
섹션 제목: “전체 예제”서로 다른 JSON 값을 가진 여러 개의 fieldFilters, metadataFilters, additionalPropertyFilters와 같은 반복되는 필터 매개변수는 requests의 딕셔너리 기반 params로는 구성할 수 없습니다 — 딕셔너리는 키당 하나의 값만 가질 수 있습니다. 대신 urllib.parse.urlencode와 튜플 목록을 사용해 직접 쿼리 문자열을 구성하세요.
import jsonimport requestsfrom urllib.parse import urlencode
API_URL = "https://<your-regional-api-url>"TOKEN = "<access_token>"CONTEXT_ID = "<twin-id>"
headers = {"Authorization": f"Bearer {TOKEN}"}
params = [ ("fieldFilters", json.dumps({"key": "type", "values": ["poi"]})), ("metadataFilters", json.dumps({"key": "inspection.pressure", "values": ["range:[80..120]"]})), ("sortBy", "name"), ("sortDir", "ASC"),]
response = requests.get( f"{API_URL}/v1/twin/{CONTEXT_ID}/search?{urlencode(params)}", headers=headers,).json()
for result in response["results"]: print(result["type"], result["name"], result["id"])노드 검색
섹션 제목: “노드 검색”GET /v1/nodes/search는 트윈 내부의 business object가 아니라, 조직의 노드 계층 구조 — 디비전, 사이트, 폴더, 트윈, asset library, 데이터 파일 — 를 검색합니다. 조직은 매개변수로 전달되지 않고 access token에서 확인됩니다.
쿼리 매개변수
섹션 제목: “쿼리 매개변수”| 매개변수 | 타입 | 참고 |
|---|---|---|
nodeTypes | DataNodeType 배열 | 하나 이상의 노드 유형으로 필터링합니다. 매개변수를 반복하세요(예: ?nodeTypes=Site&nodeTypes=Folder). 값: Organization, Site, Pointcloud, Mesh, SiteFile, Folder, Project, Division, Artifact, DataBundle, Twin, AssetLibrary. |
excludeNodeTypes | DataNodeType 배열 | 하나 이상의 노드 유형을 제외합니다. 동일한 반복 구문. |
searchNameQuery | string | 노드 이름으로 필터링. |
nameMatch | exact | contains | searchNameQuery의 매치 모드. 기본값 contains. |
parentId | uuid | 이 노드의 직계 자식만. |
ancestorId | uuid | 조상 자신을 제외한 모든 깊이의 자손. 사이트나 폴더 아래의 중첩된 콘텐츠에는 이것을 사용하고, 직계 자식만 필요하면 parentId를 사용하세요. |
createdById | uuid | 생성자로 필터링. |
createdBefore / createdAfter | timestamp | 엄격하게 이전/이후. 형식 YYYY-MM-DDTHH:mm:ss. |
updatedBefore / updatedAfter | timestamp | 동일한 형식. |
bytesStored | 정수 | 정확히 일치. |
minBytesStored / maxBytesStored | 정수 | 포함 경계. |
childrenCount | 정수 | 정확히 일치. |
minChildrenCount / maxChildrenCount | 정수 | 포함 경계. |
includesThumbnail | 'true' | 'false' | 가능한 경우 서명된 썸네일 URL을 포함합니다. 기본값 'false'. |
page | 정수 | 기본값 1. |
limit | 정수 | 1–50, 기본값 20. |
sortBy | name | createdAt | updatedAt | bytesStored | childrenCount | 정렬 필드. |
sortDir | ASC | DESC | 정렬 방향. 기본값 ASC. |
{ "items": [ { "id": "a6f75b3c-f261-4b27-9a2a-9a6cc1234c53", "createdAt": "2026-06-01T10:00:00.000Z", "updatedAt": "2026-06-01T10:00:00.000Z", "name": "Building A", "type": "Site", "createdById": "8f0b9a2c-8f1b-4a10-9e1c-0b3a2d9a4b7d", "parentId": "d2a1c4b7-3e10-4f2c-9a6b-7c1d8e2f3a45", "childrenCount": 4, "bytesStored": 15728640, "thumbnailSignedUrl": null } ], "total": 1}특정 조상 노드 아래의 모든 사이트:
GET {api_url}/v1/nodes/search?nodeTypes=Site&ancestorId=d2a1c4b7-3e10-4f2c-9a6b-7c1d8e2f3a45Authorization: Bearer {access_token}이름이 “Archive”로 시작하는 폴더를 이름순으로 정렬:
GET {api_url}/v1/nodes/search?nodeTypes=Folder&searchNameQuery=Archive&nameMatch=contains&sortBy=name&sortDir=ASCAuthorization: Bearer {access_token}import requestsfrom urllib.parse import urlencode
API_URL = "https://<your-regional-api-url>"TOKEN = "<access_token>"ANCESTOR_ID = "<division-or-site-id>"
headers = {"Authorization": f"Bearer {TOKEN}"}
params = [ ("nodeTypes", "Site"), ("ancestorId", ANCESTOR_ID), ("sortBy", "name"), ("sortDir", "ASC"),]
response = requests.get( f"{API_URL}/v1/nodes/search?{urlencode(params)}", headers=headers,).json()
for node in response["items"]: print(node["type"], node["name"], node["id"])다음 단계
섹션 제목: “다음 단계”노드 검색으로 반환된 노드 아래에 리소스를 생성하려면 Data Bundle 생성을 참조하세요.