Content

최종 수정: 2026년 7월 3일

ContentContent Type(틀)으로 찍어낸 실제 데이터 한 건입니다. 옷가게 쇼핑몰을 예로 들면, Content Type "상품"이 상품명·가격·상세 설명 같은 항목 구성을 규정하고, "스테인리스 텀블러 500ml" 한 개가 그 틀을 따르는 Content 한 건입니다.

Content는 두 부분으로 이루어집니다. fields에는 각 필드의 실제 값이 들어가고, sys에는 발행·버전·보관 같은 상태가 담깁니다. CMA에서 ContentSpace 하위 리소스이며, 조회·삭제 경로는 /spaces/{spaceId}/contents를, 생성·수정 경로는 Content Type 하위인 /spaces/{spaceId}/content-types/{contentTypeId}/contents를 기준으로 합니다. 관리 작업은 CMA에서 수행하고, 발행된 스냅샷은 CDA로 전달됩니다.

리소스 구조

다음은 발행된 Content "스테인리스 텀블러 500ml"의 단일 조회 응답입니다. sys(시스템 속성)와 함께 fields(필드 값)와 metadata(태그 등 부가 정보)를 가집니다.

{
  "sys": {
    "id": "3trmXRM3RqbgSnifyg7PUl8DzDgDzP",
    "type": "Content",
    "space": { "sys": { "id": "HnQ32YiH", "type": "Refer", "targetType": "Space" } },
    "contentType": { "sys": { "id": "3trmXRLdJF4GBlAjtcuoZ7Pnxj8dlA", "type": "Refer", "targetType": "ContentType" } },
    "publish": {
      "version": 1,
      "at": "2026-06-18T09:51:44.128Z",
      "firstAt": "2026-06-18T09:51:44.128Z",
      "counter": 1,
      "by": { "sys": { "id": "3p4tcFbQRwz503VXdtHXNI5dZH5TVB", "type": "Refer", "targetType": "User" } }
    },
    "createdBy": { "sys": { "id": "3p4tcFbQRwz503VXdtHXNI5dZH5TVB", "type": "Refer", "targetType": "User" } },
    "createdAt": "2026-06-18T09:51:14.597Z",
    "updatedBy": { "sys": { "id": "3p4tcFbQRwz503VXdtHXNI5dZH5TVB", "type": "Refer", "targetType": "User" } },
    "updatedAt": "2026-06-18T09:51:44.128Z",
    "version": 2,
    "status": "Published"
  },
  "fields": {
    "price": { "en-US": 18000 },
    "productName": { "en-US": "Stainless Tumbler 500ml", "ko-KR": "스테인리스 텀블러 500ml" }
  },
  "metadata": { "tags": [] }
}

주요 키:

  • sys.id: Content의 고유 식별자입니다. 단일 조회·수정·삭제·발행 경로의 {contentId}에 들어갑니다.
  • sys.contentType: 이 Content가 따르는 Content Type을 가리키는 Refer입니다.
  • fields: 각 필드의 값입니다. 키는 필드의 apiName이며, 값은 로케일별 맵입니다. 아래 필드 키는 apiName이다에서 설명합니다.
  • metadata.tags: 이 Content에 단 Tag 목록입니다. 각 항목은 Refer<Tag> 모양이며, 단 태그가 없으면 빈 배열 []입니다.

필드 키는 apiName이다

fields 객체의 키는 각 필드의 apiName입니다. Content Type의 내부 id나 콘텐츠 스튜디오에 보이는 필드 이름(예: 상품명)이 아닙니다. 따라서 Content를 만들거나 읽을 때는 Content Type에 정의된 apiName을 키로 써야 합니다.

데모 "상품" Content Type의 필드와 그 apiName 매핑은 다음과 같습니다.

콘텐츠 스튜디오 이름apiNametypelocalizedrequired
상품명productNameShortTexttruetrue
가격priceLongfalsefalse
상세 설명descriptionRichTexttruefalse
대표 사진photoRefer<Media>falsefalse
브랜드brandReferfalsefalse

각 필드 값은 로케일별 맵입니다. 값의 모양은 localized에 따라 다릅니다.

  • localized: true 필드{ "<locale>": 값 } 형태로 여러 로케일 값을 가질 수 있습니다. 예: "productName": { "en-US": "Stainless Tumbler 500ml", "ko-KR": "스테인리스 텀블러 500ml" }.
  • localized: false 필드(비종속 필드)는 Space 기본 Locale 키 하나에만 값을 넣습니다. 다른 로케일 키는 넣을 수 없습니다. 데모 Space의 기본 Localeen-US이므로, 가격은 "price": { "en-US": 18000 }처럼 en-US 키 하나만 가집니다.

기본 Locale이 무엇인지, required 필드를 어느 로케일까지 채워야 하는지, 값이 없을 때의 fallback 규칙은 다국어 (개념)에서 다룹니다.

타입별 값 모양

로케일 키 안에 들어가는 값의 모양은 필드의 type을 따릅니다. 대부분의 타입은 그 타입의 값을 그대로 넣습니다(텍스트 타입은 문자열, Number·Long은 숫자, Booleantrue/false). 모양이 객체나 배열이라 헷갈리기 쉬운 타입은 다음과 같습니다.

  • Location: { "latitude": <숫자>, "longitude": <숫자> } 객체입니다. latitude는 -90 이상 90 이하, longitude는 -180 이상 180 이하이며, 두 키 모두 필수입니다. 정의된 키 외에는 받지 않으므로 lat·lng·lon 같은 줄임 키를 쓰면 거부됩니다.
  • Refer: 대상을 가리키는 Refer 객체이며, id 문자열만 넣지 않고 sys 객체를 그대로 넣습니다. 다른 Content를 가리키면 targetTypeContent, Media를 가리키면 Media입니다. Refer 모양 자체는 시스템 속성 (sys)의 Refer 모양에서 정의합니다.
  • Array: 원소 타입의 값을 담은 JSON 배열입니다. 원소가 텍스트면 문자열 배열이고, 원소가 Refer(Content 또는 Media)면 Refer 객체의 배열입니다. id 문자열의 배열이 아닙니다.

다음은 fields 안에 들어가는 값의 예입니다(Space 기본 Localeen-US인 경우).

{
  "location": { "en-US": { "latitude": 37.5662, "longitude": 126.9910 } },
  "tags": { "en-US": ["신상", "한정판"] },
  "photo": { "en-US": { "sys": { "type": "Refer", "id": "3trmXRM3RqbgSnifyg7PUl8DzDgDzP", "targetType": "Media" } } },
  "photos": { "en-US": [ { "sys": { "type": "Refer", "id": "3trmXRM3RqbgSnifyg7PUl8DzDgDzP", "targetType": "Media" } } ] }
}

시스템 속성 (sys)

모든 Content는 공통 시스템 속성을 sys 객체에 담습니다. space, contentType, createdBy, updatedByRefer 모양({ "sys": { "id", "type": "Refer", "targetType" } })으로 들어갑니다.

속성타입설명
idstring리소스 고유 식별자.
typestring리소스 종류. Content는 항상 "Content".
spaceRefer<Space>Content가 속한 Space.
contentTypeRefer<ContentType>Content가 따르는 Content Type.
publishobject발행 상태 포인터. 아래 키 참조.
archiveobject보관 정보. 보관 중일 때만 존재하며, 아니면 키가 없습니다. 아래 키 참조.
createdByRefer<User>생성한 사용자.
createdAtstring (date-time)생성 시각.
updatedByRefer<User>마지막으로 수정한 사용자.
updatedAtstring (date-time)마지막 수정 시각.
versioninteger (≥1)리소스 버전. 생성·수정·발행·발행취소·보관 등 모든 변경마다 1씩 올라갑니다.
statusstring (enum)발행 상태. 아래 4가지 중 하나.

status는 다음 4가지 중 하나입니다.

status의미
Draft작성 중이며 아직 발행되지 않은 상태.
Changed발행된 적이 있으나 그 뒤 수정되어 아직 발행되지 않은 변경분이 있는 상태.
Published발행되어 있고 미발행 변경분이 없는 상태.
Archived보관된 상태.

publish 객체는 발행 상태를 가리키는 포인터입니다. 발행 중일 때는 다음 키를 모두 가집니다.

타입설명
versioninteger발행된 시점의 sys.version.
atstring (date-time)마지막 발행 시각.
firstAtstring (date-time)최초 발행 시각. 발행취소해도 보존됩니다.
counterinteger누적 발행 횟수.
byRefer<User>마지막으로 발행한 사용자.

발행취소하면 publish에서 version·at·by가 빠지고 firstAt·counter만 남습니다. 한 번도 발행한 적이 없으면 publish{ "counter": 0 }입니다.

archive 객체는 보관 중일 때만 존재합니다. 보관 중이면 version(보관 시점의 sys.versionat(보관 시각)·by(보관한 사용자)를 가지며, 보관 상태가 아니면 archive 키 자체가 없습니다.

아래 예시의 sys.version과 모든 시각 값은 실제 호출 시점의 값이며, 호출마다 달라집니다.

발행과 버전, 동시성

Content의 라이프사이클은 다음과 같습니다.

  • 생성하면 statusDraft입니다. Content는 생성 시 자동으로 발행되지 않습니다. 이 점이 Content Type과 다릅니다. Content Type은 생성과 동시에 자동 발행되지만, Content는 별도 발행 호출이 있어야 전달 경로에 오릅니다.
  • 발행하면 statusPublished가 됩니다.
  • 발행한 뒤 수정하면 statusChanged가 됩니다. 미발행 변경분이 있다는 뜻입니다.
  • 발행취소하면 status가 다시 Draft가 됩니다.
  • 보관하려면 먼저 발행취소해야 합니다. 발행 상태인 Content는 바로 보관할 수 없습니다.

sys.version은 변경마다 1씩 올라갑니다.

수정·부분 수정·발행·발행취소·보관·보관해제 요청에는 x-weegloo-version 헤더에 현재 sys.version을 실어야 합니다. 이 값이 빠지거나 현재 버전과 어긋나면 동시 수정 충돌로 간주되어 요청이 거부됩니다. 생성과 삭제 요청에는 이 헤더가 없습니다. 발행·발행취소·보관·보관해제 같은 상태 전환에는 별도 요청 본문이 없습니다.

API

아래 모든 엔드포인트의 기준 URL은 https://cma.weegloo.com/v1이며, Authorization 헤더에 CMA를 인증하는 Bearer 토큰이 필요합니다. 수정·부분 수정·발행·발행취소·보관·보관해제는 낙관적 동시성 제어를 위해 X-Weegloo-Version 헤더(현재 리소스의 sys.version)를 함께 보내야 합니다.

/contents 목록(GET /spaces/{spaceId}/contents)을 fields.*로 필터하거나 정렬할 때는 sys.contentType.sys.id={contentTypeId}Content Type을 함께 지정해야 합니다. contentType={contentTypeId} 형태로는 대신할 수 없습니다. /content-types/{contentTypeId}/contentsContent Type이 경로에 명시되어 있어 따로 지정할 필요가 없습니다.

  • Content Type: 이 Content의 틀(필드 정의·apiName).
  • CDA Content: 발행된 Content를 방문자에게 전달(읽기).
  • Tag: metadata.tags에 다는 분류 라벨.
  • 다국어 (개념): 기본 Locale·필수 채움·fallback.