Content
Content는 Content Type(양식)으로 찍어낸 실제 데이터 한 건입니다. 옷가게 쇼핑몰을 예로 들면, Content Type "상품"이 상품명·가격·상세 설명 같은 항목 구성을 규정하고, "스테인리스 텀블러 500ml" 한 개가 그 양식을 따르는 Content 한 건입니다.
Content는 두 부분으로 이루어집니다. fields에는 각 필드의 실제 값이 들어가고, sys에는 발행·버전·보관 같은 상태가 담깁니다. CMA에서 Content는 Space 하위 리소스이며, 조회·삭제 경로는 /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 매핑은 다음과 같습니다.
| 콘텐츠 스튜디오 이름 | apiName | type | localized | required |
|---|---|---|---|---|
| 상품명 | productName | ShortText | true | true |
| 가격 | price | Long | false | false |
| 상세 설명 | description | RichText | true | false |
| 대표 사진 | photo | Refer<Media> | false | false |
| 브랜드 | brand | Refer | false | false |
각 필드 값은 로케일별 맵입니다. 값의 모양은 localized에 따라 다릅니다.
localized: true필드는{ "<locale>": 값 }형태로 여러 로케일 값을 가질 수 있습니다. 예:"productName": { "en-US": "Stainless Tumbler 500ml", "ko-KR": "스테인리스 텀블러 500ml" }.localized: false필드(비종속 필드)는 Space 기본 Locale 키 하나에만 값을 넣습니다. 다른 로케일 키는 넣을 수 없습니다. 데모 Space의 기본 Locale이en-US이므로, 가격은"price": { "en-US": 18000 }처럼en-US키 하나만 가집니다.
기본 Locale이 무엇인지, required 필드를 어느 로케일까지 채워야 하는지, 값이 없을 때의 fallback 규칙은 다국어 (개념)에서 다룹니다.
타입별 값 모양
로케일 키 안에 들어가는 값의 모양은 필드의 type을 따릅니다. 대부분의 타입은 그 타입의 값을 그대로 넣습니다(텍스트 타입은 문자열, Number·Long은 숫자, Boolean은 true/false). 모양이 객체나 배열이라 헷갈리기 쉬운 타입은 다음과 같습니다.
Location:{ "latitude": <숫자>, "longitude": <숫자> }객체입니다.latitude는 -90 이상 90 이하,longitude는 -180 이상 180 이하이며, 두 키 모두 필수입니다. 정의된 키 외에는 받지 않으므로lat·lng·lon같은 줄임 키를 쓰면 거부됩니다.Refer: 대상을 가리키는Refer객체이며, id 문자열만 넣지 않고sys객체를 그대로 넣습니다. 다른 Content를 가리키면targetType이Content, Media를 가리키면Media입니다.Refer모양 자체는 시스템 속성 (sys)의 Refer 모양에서 정의합니다.Array: 원소 타입의 값을 담은 JSON 배열입니다. 원소가 텍스트면 문자열 배열이고, 원소가Refer(Content 또는 Media)면Refer객체의 배열입니다. id 문자열의 배열이 아닙니다.
다음은 fields 안에 들어가는 값의 예입니다(Space 기본 Locale이 en-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, updatedBy는 Refer 모양({ "sys": { "id", "type": "Refer", "targetType" } })으로 들어갑니다.
| 속성 | 타입 | 설명 |
|---|---|---|
id | string | 리소스 고유 식별자. |
type | string | 리소스 종류. Content는 항상 "Content". |
space | Refer<Space> | 이 Content가 속한 Space. |
contentType | Refer<ContentType> | 이 Content가 따르는 Content Type. |
publish | object | 발행 상태 포인터. 아래 키 참조. |
archive | object | 보관 정보. 보관 중일 때만 존재하며, 아니면 키가 없습니다. 아래 키 참조. |
createdBy | Refer<User> | 생성한 사용자. |
createdAt | string (date-time) | 생성 시각. |
updatedBy | Refer<User> | 마지막으로 수정한 사용자. |
updatedAt | string (date-time) | 마지막 수정 시각. |
version | integer (≥1) | 리소스 버전. 생성·수정·발행·발행취소·보관 등 모든 변경마다 1씩 올라갑니다. |
status | string (enum) | 발행 상태. 아래 4가지 중 하나. |
status는 다음 4가지 중 하나입니다.
status | 의미 |
|---|---|
Draft | 작성 중이며 아직 발행되지 않은 상태. |
Changed | 발행된 적이 있으나 그 뒤 수정되어 아직 발행되지 않은 변경분이 있는 상태. |
Published | 발행되어 있고 미발행 변경분이 없는 상태. |
Archived | 보관된 상태. |
publish 객체는 발행 상태를 가리키는 포인터입니다. 발행 중일 때는 다음 키를 모두 가집니다.
| 키 | 타입 | 설명 |
|---|---|---|
version | integer | 발행된 시점의 sys.version. |
at | string (date-time) | 마지막 발행 시각. |
firstAt | string (date-time) | 최초 발행 시각. 발행취소해도 보존됩니다. |
counter | integer | 누적 발행 횟수. |
by | Refer<User> | 마지막으로 발행한 사용자. |
발행취소하면 publish에서 version·at·by가 빠지고 firstAt·counter만 남습니다. 한 번도 발행한 적이 없으면 publish는 { "counter": 0 }입니다.
archive 객체는 보관 중일 때만 존재합니다. 보관 중이면 version(보관 시점의 sys.version)·at(보관 시각)·by(보관한 사용자)를 가지며, 보관 상태가 아니면 archive 키 자체가 없습니다.
아래 예시의 sys.version과 모든 시각 값은 실제 호출 시점의 값이며, 호출마다 달라집니다.
발행과 버전, 동시성
Content의 라이프사이클은 다음과 같습니다.
- 생성하면
status는Draft입니다.Content는 생성 시 자동으로 발행되지 않습니다. 이 점이 Content Type과 다릅니다. Content Type은 생성과 동시에 자동 발행되지만, Content는 별도 발행 호출이 있어야 전달 경로에 오릅니다. - 발행하면
status가Published가 됩니다. - 발행한 뒤 수정하면
status가Changed가 됩니다. 미발행 변경분이 있다는 뜻입니다. - 발행취소하면
status가 다시Draft가 됩니다. - 보관하려면 먼저 발행취소해야 합니다. 발행 상태인 Content는 바로 보관할 수 없습니다.
sys.version은 변경마다 1씩 올라갑니다.
수정·부분 수정·발행·발행취소·보관·보관해제 요청에는 x-weegloo-version 헤더에 현재 sys.version을 실어야 합니다. 이 값이 빠지거나 현재 버전과 어긋나면 동시 수정 충돌로 간주되어 요청이 거부됩니다. 생성과 삭제 요청에는 이 헤더가 없습니다. 발행·발행취소·보관·보관해제 같은 상태 전환에는 별도 요청 본문이 없습니다.
값 제한
필드 값에는 Content Type에서 건 validations 외에 타입이 정하는 플랫폼 상한이 걸립니다. 타입별 상한은 Content Type의 필드 종류 표에 있습니다.
이 상한은 Content에 값을 쓸 때 검사하고, 발행할 때 한 번 더 검사합니다. validations의 size로 더 큰 상한을 적어도 플랫폼 상한이 먼저 걸리므로, ShortText 필드에 size.max를 200으로 두어도 65자부터 거부됩니다.
validations는 항목에 따라 검사 시점이 다릅니다. unique, mediaMimetypeGroup, mediaImageDimensions, mediaFileSize, referContentType은 발행에서만 검사합니다. 나머지 항목과 타입·플랫폼 상한은 쓰기와 발행 양쪽에서 검사합니다. 따라서 쓰기를 통과한 값이 발행에서 거부될 수 있습니다.
Location과 Refer는 정의된 키 외에는 받지 않습니다. lat처럼 줄인 키나 sys 아래의 낯선 키를 넣으면 거부됩니다.
오류
필드 값 검증에서 나오는 코드입니다. 발행·버전·상태 전환에서 나오는 코드는 아직 이 표에 정리돼 있지 않습니다. 모든 리소스에 공통인 코드는 공통 오류를 참조하세요.
| 코드 | 조건 |
|---|---|
WGL400006 | 본문의 필드 값·필드 키·Locale 중 하나가 규칙에 어긋났습니다. 값이 타입이나 플랫폼 상한을 벗어난 경우, Content Type에 정의되지 않은 필드 키를 보낸 경우, required 필드의 값이 빠진 경우, Space에 없는 Locale 키를 쓴 경우가 모두 이 코드입니다. 어긋난 항목은 응답의 details.errors에 필드 경로와 함께 담깁니다. 생성·수정·부분 수정과 발행이 모두 이 검사를 받으며, 발행이 검사하는 범위가 더 넓습니다. |
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}/contents는 Content Type이 경로에 명시되어 있어 따로 지정할 필요가 없습니다.
관련 문서
- Content Type: 이 Content의 양식(필드 정의·apiName).
- CDA Content: 발행된 Content를 방문자에게 전달(읽기).
- Tag: metadata.tags에 다는 분류 라벨.
- 다국어 (개념): 기본 Locale·필수 채움·fallback.
