템플릿 API
알림톡 템플릿을 등록하고 검수를 요청·관리하는 API입니다. 템플릿은 카카오 심사를 통과(승인)한 뒤에만 발송에 사용할 수 있으며, 심사는 카카오가 수행하고 알마니가 상태를 동기화해 제공합니다.
템플릿 상태
일반적인 흐름은 REG → REQ → APR 또는 REJ 이며, 반려된 템플릿은 수정 후 재검수 요청으로 다시 REQ 상태가 됩니다.
| 상태 | 의미 | 설명 |
|---|---|---|
| REG | 등록 | 검수 요청 전 초안 상태입니다. autoReview: false로 등록하거나
검수 요청을 취소하면 이 상태가 됩니다. |
| REQ | 검수중 | 카카오 심사가 진행 중입니다. |
| APR | 승인 | 심사를 통과했습니다. 발송에 사용할 수 있습니다(sendable: true). |
| REJ | 반려 | 심사에서 반려되었습니다. rejectReason으로 사유를 확인하고
수정 후 재검수를 요청하세요. |
| UPT | 수정중 | 템플릿을 수정하고 있는 상태입니다. 재검수 요청 대상입니다. |
https://docs.almani-center.com/open/v1 입니다.
모든 요청에는 인증에서 발급한
API 키(Authorization: Bearer ak_live_xxxx — 예시는 더미 키)와
대상 사이트를 지정하는 X-Site-Id 헤더가 필요합니다.
템플릿 생성과 검수 요청 API에는 Idempotency-Key 헤더(UUID 권장) 사용을 권장합니다.
meta.quota.templateReview에서 잔여 한도를 확인할 수 있으며,
모든 응답의 meta.quota.rateLimit에는 분당 요청 한도가 항상 포함됩니다.
에러 응답 형식과 전체 에러 코드는
에러 형식과 코드를 참고하세요.
개인화 변수 카탈로그
/variables
필요 스코프: template:read
템플릿 변수 매핑(mappingData)에 사용할 수 있는 개인화 변수 목록을 반환합니다.
사이트의 플랫폼 기준으로 사용 가능한 변수만 반환하며,
triggerEvent를 지정하면 해당 트리거가 제공하는 변수만 조회합니다.
#{변수명} 형식으로 사용합니다.
자세한 개념은 개인화 변수 가이드를 참고하세요.
Query 파라미터
트리거 값입니다 (예: orderComplete, deliveryStart).
미지정 시 전체 변수를 반환합니다.
Response
사용 가능한 개인화 변수 목록입니다.
개인화 변수명(한글 표준명)입니다. 매핑과 #{변수명}에 이 이름을 사용합니다.
변수 그룹입니다 (예: 고객, 주문, 배송).
변수 설명입니다.
이 변수를 제공하는 트리거 목록입니다.
# 트리거별 필터: /variables?triggerEvent=deliveryStart
curl "https://docs.almani-center.com/open/v1/variables" \
-H "Authorization: Bearer ak_live_xxxx" \
-H "X-Site-Id: 1024"
// Java 17 + Spring Framework 6 RestClient
RestClient restClient = RestClient.builder()
.baseUrl("https://docs.almani-center.com/open/v1")
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer ak_live_xxxx")
.defaultHeader("X-Site-Id", "1024")
.build();
String response = restClient.get()
.uri("/variables")
// 트리거별 필터:
// .uri(b -> b.path("/variables")
// .queryParam("triggerEvent", "deliveryStart").build())
.retrieve()
.body(String.class);
HTTP/1.1 200 OK
{
"code": "0000",
"message": "OK",
"data": {
"variables": [
{ "name": "고객명", "group": "고객",
"description": "주문자 이름",
"triggers": ["orderComplete", "deliveryStart"] },
{ "name": "주문번호", "group": "주문",
"description": "플랫폼 주문번호",
"triggers": ["orderComplete", "deliveryStart"] },
{ "name": "송장번호", "group": "배송",
"description": "택배 송장번호",
"triggers": ["deliveryStart"] }
]
},
"meta": {
"requestId": "req_2b91c4e7",
"quota": {
"rateLimit": {
"limit": 120,
"remaining": 119,
"resetAt": "2026-07-25T14:31:00+09:00"
}
}
}
}
HTTP/1.1 400 Bad Request
{
"code": "4003",
"message": "X-Site-Id 헤더가 누락되었습니다. GET /sites로 접근 가능한 사이트를 확인해 주세요.",
"data": null,
"meta": {
"requestId": "req_0a4d81c9",
"quota": {
"rateLimit": {
"limit": 120,
"remaining": 118,
"resetAt": "2026-07-25T14:31:00+09:00"
}
}
}
}
템플릿 신청
/templates
필요 스코프: template:write
템플릿을 등록하고, 기본값으로는 즉시 카카오 검수까지 요청합니다.
성공 시 templateCode가 발급되고 상태는
REQ가 됩니다.
autoReview: false를 보내면
REG 초안으로만 저장됩니다.
Request Body
템플릿 이름입니다. 최대 30자, 사이트 내 중복 불가(중복 시 4091).
템플릿 유형입니다.
BA 기본형 · IM 이미지형 ·
EX 강조표기형 · IT 아이템리스트형.
알림톡 템플릿 카테고리 코드입니다.
메시지 본문입니다. 변수는 #{변수명} 형식으로 넣습니다.
이미지형은 최대 400자, 그 외 유형은 최대 1,000자입니다.
변수만으로 구성된 본문은 반려됩니다.
보안 템플릿 여부입니다(메인 기기 외 노출 제한). 기본 false.
검수 요청 의견입니다. 최대 500자, 카카오 심사 담당자에게 전달됩니다.
버튼 목록입니다. 최대 5개. CTA 랜딩은 WL(웹링크)을 사용합니다.
버튼 문구입니다. 최대 14자.
버튼 유형입니다.
WL 웹링크 · AL 앱링크 · DS 배송조회 ·
BK 봇키워드 · MD 메시지전달 · AC 채널추가.
모바일 랜딩 URL입니다(https). 변수를 포함할 수 있습니다.
PC 랜딩 URL입니다.
앱 스킴입니다.
강조표기형 요소입니다 —
{ "title": "강조 타이틀(변수 가능)", "subtitle": "보조 문구" }.
이미지형 요소입니다 — { "imageUrl": "https://…" }.
사전 업로드된 이미지 URL을 사용합니다(권장 규격 800×400).
아이템리스트형 요소입니다 —
{ "header", "items": [{"title","description"}](2~10개), "highlight": {"title","description"} }.
템플릿 변수 → 개인화 변수 매핑입니다. 템플릿 변수명이 표준 개인화 변수명과 같으면 생략할 수 있습니다(자동 매핑). 이름이 다를 때만 명시합니다.
기본 true — 등록 즉시 검수를 요청합니다.
false면 REG 초안으로만 저장합니다.
대상 트리거 값입니다. 지정하면 해당 트리거가 제공하는 개인화 변수를 기준으로
mappingData를 검사합니다.
4000 에러와 함께 사용할 수 없는 매핑 대상을 안내합니다.
usedFor(대상 트리거)를 함께 보내면 해당 트리거가 제공하는 변수 기준으로
더 엄격히 검사합니다. 본문의 #{변수} 중 매핑(자동 매핑 포함)이 해석되지
않는 것이 있으면 응답의 data.unmappedVariables로 알려주며,
발송 연결 전까지 매핑을 완성해야 합니다.
발송 시점에 매핑 값이 비어 있는 수신자는 발송을 건너뜁니다.
이 템플릿을 발송에 연결하는 작업(캠페인 설정)은 알마니 콘솔에서 수행하며,
그때 이 매핑이 기본값으로 상속됩니다.
Response
숫자 ID입니다(참고용).
알마니가 발급하는 TPL_ 접두사의 불투명한 템플릿 코드입니다.
이후 모든 조회·검수 요청의 키로 사용합니다.
REQ(autoReview) 또는 REG.
autoReview: true일 때의 검수 요청 시각입니다. 초안 생성 시 null입니다.
해석되지 않은 개인화 변수가 있을 때만 포함됩니다.
주요 에러
| 코드 | HTTP | 설명 |
|---|---|---|
4091 | 409 | 템플릿명 중복 |
4291 | 429 | 일일 템플릿 검수 요청 한도 초과 |
6001 | 502 | autoReview: true에서 발신프로필 미등록 또는 유효하지 않음 —
발신프로필 참조 |
curl -X POST "https://docs.almani-center.com/open/v1/templates" \
-H "Authorization: Bearer ak_live_xxxx" \
-H "X-Site-Id: 1024" \
-H "Idempotency-Key: 4f1c9a2e-0b8e-4c3f-9e1a-7b6d2f8c4a10" \
-H "Content-Type: application/json" \
-d '{
"templateName": "주문완료 안내 v3",
"templateType": "BA",
"category": "004001",
"content": "#{고객명}님, 주문이 완료되었습니다.\n\n▶ 주문번호: #{주문번호}\n▶ 결제금액: #{결제금액}원\n\n배송이 시작되면 다시 알려드릴게요.",
"comments": "주문 완료 시 발송되는 정보성 메시지입니다.",
"buttons": [
{
"name": "주문 상세 보기",
"type": "WL",
"urlMobile": "https://shop.example.com/orders/#{주문번호}"
}
],
"mappingData": {
"고객명": "고객명",
"주문번호": "주문번호",
"결제금액": "결제금액"
}
}'
// Java 17 + Spring Framework 6 RestClient
RestClient restClient = RestClient.builder()
.baseUrl("https://docs.almani-center.com/open/v1")
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer ak_live_xxxx")
.defaultHeader("X-Site-Id", "1024")
.build();
record TemplateButton(String name, String type, String urlMobile) {}
record CreateTemplateRequest(
String templateName, String templateType, String category,
String content, String comments,
List<TemplateButton> buttons,
Map<String, String> mappingData) {}
var request = new CreateTemplateRequest(
"주문완료 안내 v3", "BA", "004001",
"#{고객명}님, 주문이 완료되었습니다.\n\n▶ 주문번호: #{주문번호}\n"
+ "▶ 결제금액: #{결제금액}원\n\n배송이 시작되면 다시 알려드릴게요.",
"주문 완료 시 발송되는 정보성 메시지입니다.",
List.of(new TemplateButton("주문 상세 보기", "WL",
"https://shop.example.com/orders/#{주문번호}")),
Map.of("고객명", "고객명", "주문번호", "주문번호", "결제금액", "결제금액"));
String response = restClient.post()
.uri("/templates")
.header("Idempotency-Key", UUID.randomUUID().toString())
.contentType(MediaType.APPLICATION_JSON)
.body(request)
.retrieve()
.body(String.class);
HTTP/1.1 200 OK
{
"code": "0000",
"message": "OK",
"data": {
"templateId": 5811,
"templateCode": "TPL_20260725143012",
"approveStatus": "REQ",
"requestedAt": "2026-07-25T14:30:12+09:00"
},
"meta": {
"requestId": "req_9f83ab21",
"quota": {
"rateLimit": {
"limit": 120,
"remaining": 117,
"resetAt": "2026-07-25T14:31:00+09:00"
},
"templateReview": {
"dailyLimit": 30,
"usedToday": 5,
"remaining": 25,
"resetAt": "2026-07-26T00:00:00+09:00"
}
}
}
}
HTTP/1.1 429 Too Many Requests
{
"code": "4291",
"message": "일일 템플릿 검수 요청 한도를 초과했습니다. 다음 날 자정 이후 다시 시도해 주세요.",
"data": null,
"meta": {
"requestId": "req_66d1f0a3",
"quota": {
"rateLimit": {
"limit": 120,
"remaining": 116,
"resetAt": "2026-07-25T14:31:00+09:00"
},
"templateReview": {
"dailyLimit": 30,
"usedToday": 30,
"remaining": 0,
"resetAt": "2026-07-26T00:00:00+09:00"
}
}
}
}
템플릿 목록 조회
/templates
필요 스코프: template:read
사이트의 사용자 생성 템플릿 중 사용 중이며 삭제되지 않은 목록을 조회합니다. 기본 제공 템플릿은 포함하지 않습니다. 상태 필터와 커서 페이지네이션(공통 규약 참조)을 지원합니다.
Query 파라미터
상태 필터입니다 — REG|REQ|APR|REJ|UPT.
복수 상태는 콤마로 구분합니다 (예: status=REQ,REJ).
템플릿명 또는 템플릿 코드 부분 일치 검색입니다.
페이지 크기입니다. 기본 20, 최대 100.
이전 응답의 data.nextCursor 값입니다. 없으면 첫 페이지를 반환합니다.
Response
템플릿 코드입니다.
템플릿 이름입니다.
REG/REQ/APR/REJ/UPT.
BA/IM/EX/IT.
가장 최근 반려 사유입니다. 반려 이력이 없으면 null입니다.
발송에 연결되어 사용 중인 템플릿인지 여부입니다.
생성 / 수정 시각입니다.
null이면 마지막 페이지입니다.
curl "https://docs.almani-center.com/open/v1/templates?status=REQ,REJ" \
-H "Authorization: Bearer ak_live_xxxx" \
-H "X-Site-Id: 1024"
// Java 17 + Spring Framework 6 RestClient
RestClient restClient = RestClient.builder()
.baseUrl("https://docs.almani-center.com/open/v1")
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer ak_live_xxxx")
.defaultHeader("X-Site-Id", "1024")
.build();
String response = restClient.get()
.uri(b -> b.path("/templates")
.queryParam("status", "REQ,REJ")
.build())
.retrieve()
.body(String.class);
HTTP/1.1 200 OK
{
"code": "0000",
"message": "OK",
"data": {
"items": [
{
"templateCode": "TPL_20260725143012",
"templateName": "주문완료 안내 v3",
"approveStatus": "REQ",
"templateType": "BA",
"rejectReason": null,
"inUse": false,
"createdAt": "2026-07-25T14:30:12+09:00",
"updatedAt": "2026-07-25T14:30:12+09:00"
}
],
"nextCursor": null
},
"meta": {
"requestId": "req_c8f24b19",
"quota": {
"rateLimit": {
"limit": 120,
"remaining": 118,
"resetAt": "2026-07-25T14:31:00+09:00"
}
}
}
}
HTTP/1.1 403 Forbidden
{
"code": "4005",
"message": "키에 template:read 스코프가 없습니다. 콘솔에서 키 스코프를 확인해 주세요.",
"data": null,
"meta": {
"requestId": "req_5d20e8b4",
"quota": {
"rateLimit": {
"limit": 120,
"remaining": 117,
"resetAt": "2026-07-25T14:31:00+09:00"
}
}
}
}
템플릿 조회
/templates/{templateCode}
필요 스코프: template:read
템플릿 단건 상세와 AL_TEMPLATE.APPROVE_STATUS에 저장된 검수 상태를 조회합니다.
상태가 REQ여도 요청 처리 중
비즈톡 상태를 실시간으로 재조회하지 않습니다.
Path 파라미터
템플릿 코드입니다 (예: TPL_20260725143012).
Response
템플릿 코드 / 이름입니다.
현재 상태입니다.
가장 최근 카카오 반려 코멘트 전문입니다. 반려 이력이 없으면 null입니다.
등록 시 지정한 값입니다.
본문 원문입니다.
등록된 버튼입니다 (name/type/urlMobile/urlPc …).
유형별 부가 요소입니다.
변수 매핑입니다.
APR 여부 — 발송에 사용할 수 있는지를 나타냅니다.
최근 검수 이력 5건입니다 — { status, comment, reviewer, at }.
전체 이력은 검수 이력으로 조회합니다.
주요 에러
| 코드 | HTTP | 설명 |
|---|---|---|
4040 | 404 | 템플릿 코드에 해당하는 템플릿 없음 |
curl "https://docs.almani-center.com/open/v1/templates/TPL_20260725143012" \
-H "Authorization: Bearer ak_live_xxxx" \
-H "X-Site-Id: 1024"
// Java 17 + Spring Framework 6 RestClient
RestClient restClient = RestClient.builder()
.baseUrl("https://docs.almani-center.com/open/v1")
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer ak_live_xxxx")
.defaultHeader("X-Site-Id", "1024")
.build();
String response = restClient.get()
.uri("/templates/{templateCode}", "TPL_20260725143012")
.retrieve()
.body(String.class);
HTTP/1.1 200 OK
{
"code": "0000",
"message": "OK",
"data": {
"templateCode": "TPL_20260725143012",
"templateName": "주문완료 안내 v3",
"approveStatus": "REJ",
"rejectReason": "버튼 링크가 메시지 내용과 무관한 페이지로 연결됩니다. 주문 상세 페이지로 수정 후 재요청해 주세요.",
"templateType": "BA",
"category": "004001",
"securityEnabled": false,
"content": "#{고객명}님, 주문이 완료되었습니다.\n\n▶ 주문번호: #{주문번호}\n▶ 결제금액: #{결제금액}원\n\n배송이 시작되면 다시 알려드릴게요.",
"buttons": [
{
"name": "주문 상세 보기",
"type": "WL",
"urlMobile": "https://event.example.com/summer"
}
],
"mappingData": {
"고객명": "고객명",
"주문번호": "주문번호",
"결제금액": "결제금액"
},
"sendable": false,
"recentHistory": [
{
"status": "REJ",
"comment": "버튼 링크가 메시지 내용과 무관한 페이지로 연결됩니다. 주문 상세 페이지로 수정 후 재요청해 주세요.",
"at": "2026-07-25T15:01:10+09:00"
},
{
"status": "REQ",
"comment": "주문 완료 시 발송되는 정보성 메시지입니다.",
"at": "2026-07-25T14:30:12+09:00"
}
]
},
"meta": {
"requestId": "req_4c07d9b2",
"quota": {
"rateLimit": {
"limit": 120,
"remaining": 116,
"resetAt": "2026-07-25T14:31:00+09:00"
}
}
}
}
HTTP/1.1 404 Not Found
{
"code": "4040",
"message": "템플릿을 찾을 수 없습니다: TPL_20260725149999",
"data": null,
"meta": {
"requestId": "req_b3e17a6d",
"quota": {
"rateLimit": {
"limit": 120,
"remaining": 115,
"resetAt": "2026-07-25T14:31:00+09:00"
}
}
}
}
재검수 요청
/templates/{templateCode}/review
필요 스코프: template:write
반려(REJ) ·
초안(REG) ·
수정중(UPT) 상태 템플릿의 검수를
(재)요청합니다. 그 외 상태에서 호출하면 4090 에러가 반환됩니다.
요청 바디에 수정할 필드를 함께 보내면 수정과 재검수를 한 번에 처리합니다.
Path 파라미터
템플릿 코드입니다.
Request Body
템플릿 신청과 동일 규격입니다. 보낸 필드만 수정한 뒤 검수를 요청합니다.
카카오 심사 담당자에게 전달되는 의견입니다. 반려 사유에 대한 소명을 권장합니다.
Response
템플릿 코드입니다.
성공 시 REQ가 됩니다.
검수 요청 시각입니다.
주요 에러
| 코드 | HTTP | 설명 |
|---|---|---|
4090 | 409 | 상태 전이 불가 — REG·REJ·UPT 이외 상태에서 요청
(message에 현재 상태 포함) |
curl -X POST "https://docs.almani-center.com/open/v1/templates/TPL_20260725143012/review" \
-H "Authorization: Bearer ak_live_xxxx" \
-H "X-Site-Id: 1024" \
-H "Idempotency-Key: 8c25d1b7-3e4f-4a90-b6c2-1d9e8f7a3c50" \
-H "Content-Type: application/json" \
-d '{
"buttons": [
{
"name": "주문 상세 보기",
"type": "WL",
"urlMobile": "https://shop.example.com/orders/#{주문번호}"
}
],
"comments": "버튼 링크를 주문 상세 페이지로 수정했습니다."
}'
// Java 17 + Spring Framework 6 RestClient
RestClient restClient = RestClient.builder()
.baseUrl("https://docs.almani-center.com/open/v1")
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer ak_live_xxxx")
.defaultHeader("X-Site-Id", "1024")
.build();
record TemplateButton(String name, String type, String urlMobile) {}
record ReviewRequest(List<TemplateButton> buttons, String comments) {}
var request = new ReviewRequest(
List.of(new TemplateButton("주문 상세 보기", "WL",
"https://shop.example.com/orders/#{주문번호}")),
"버튼 링크를 주문 상세 페이지로 수정했습니다.");
String response = restClient.post()
.uri("/templates/{templateCode}/review", "TPL_20260725143012")
.header("Idempotency-Key", UUID.randomUUID().toString())
.contentType(MediaType.APPLICATION_JSON)
.body(request)
.retrieve()
.body(String.class);
HTTP/1.1 200 OK
{
"code": "0000",
"message": "OK",
"data": {
"templateCode": "TPL_20260725143012",
"approveStatus": "REQ",
"requestedAt": "2026-07-25T15:12:03+09:00"
},
"meta": {
"requestId": "req_d40a7c55",
"quota": {
"rateLimit": {
"limit": 120,
"remaining": 115,
"resetAt": "2026-07-25T15:13:00+09:00"
},
"templateReview": {
"dailyLimit": 30,
"usedToday": 6,
"remaining": 24,
"resetAt": "2026-07-26T00:00:00+09:00"
}
}
}
}
HTTP/1.1 409 Conflict
{
"code": "4090",
"message": "REG, REJ, UPT 상태의 템플릿만 검수를 요청할 수 있습니다. 현재 상태: APR",
"data": null,
"meta": {
"requestId": "req_92c6e0af",
"quota": {
"rateLimit": {
"limit": 120,
"remaining": 114,
"resetAt": "2026-07-25T15:13:00+09:00"
}
}
}
}
검수 요청 취소
/templates/{templateCode}/review
필요 스코프: template:write
REQ 상태의 검수 요청을 취소합니다.
성공 시 상태는 REG로 되돌아갑니다.
카카오 심사가 이미 시작된 뒤에는 취소가 거부될 수 있으며,
이때 6000 에러로 사유를 전달합니다.
Path 파라미터
템플릿 코드입니다.
Response
템플릿 코드입니다.
성공 시 REG로 복귀합니다.
주요 에러
| 코드 | HTTP | 설명 |
|---|---|---|
6000 | 502 | 심사 착수 후 취소 거부 (사유 포함) |
curl -X DELETE "https://docs.almani-center.com/open/v1/templates/TPL_20260725143012/review" \
-H "Authorization: Bearer ak_live_xxxx" \
-H "X-Site-Id: 1024"
// Java 17 + Spring Framework 6 RestClient
RestClient restClient = RestClient.builder()
.baseUrl("https://docs.almani-center.com/open/v1")
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer ak_live_xxxx")
.defaultHeader("X-Site-Id", "1024")
.build();
String response = restClient.delete()
.uri("/templates/{templateCode}/review", "TPL_20260725143012")
.retrieve()
.body(String.class);
HTTP/1.1 200 OK
{
"code": "0000",
"message": "OK",
"data": {
"templateCode": "TPL_20260725143012",
"approveStatus": "REG"
},
"meta": {
"requestId": "req_71e9b2cd",
"quota": {
"rateLimit": {
"limit": 120,
"remaining": 117,
"resetAt": "2026-07-25T14:31:00+09:00"
},
"templateReview": {
"dailyLimit": 30,
"usedToday": 6,
"remaining": 24,
"resetAt": "2026-07-26T00:00:00+09:00"
}
}
}
}
HTTP/1.1 502 Bad Gateway
{
"code": "6000",
"message": "카카오 심사가 이미 시작되어 검수 요청을 취소할 수 없습니다.",
"data": null,
"meta": {
"requestId": "req_ae52c703",
"quota": {
"rateLimit": {
"limit": 120,
"remaining": 116,
"resetAt": "2026-07-25T14:31:00+09:00"
}
}
}
}
검수 이력
/templates/{templateCode}/review-history
필요 스코프: template:read
템플릿의 전체 검수 이력을 반환합니다. 커서 페이지네이션(공통 규약 참조)을 지원합니다.
Path 파라미터
템플릿 코드입니다.
Query 파라미터
페이지 크기입니다. 기본 20, 최대 100.
이전 응답의 data.nextCursor 값입니다.
Response
REG | REQ | APR | REJ | UPT.
검수 요청 의견 또는 카카오 심사 코멘트입니다.
코멘트 작성 주체입니다.
이력 발생 시각입니다.
null이면 마지막 페이지입니다.
curl "https://docs.almani-center.com/open/v1/templates/TPL_20260725143012/review-history" \
-H "Authorization: Bearer ak_live_xxxx" \
-H "X-Site-Id: 1024"
// Java 17 + Spring Framework 6 RestClient
RestClient restClient = RestClient.builder()
.baseUrl("https://docs.almani-center.com/open/v1")
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer ak_live_xxxx")
.defaultHeader("X-Site-Id", "1024")
.build();
String response = restClient.get()
.uri("/templates/{templateCode}/review-history", "TPL_20260725143012")
.retrieve()
.body(String.class);
HTTP/1.1 200 OK
{
"code": "0000",
"message": "OK",
"data": {
"items": [
{
"status": "REJ",
"comment": "버튼 링크가 메시지 내용과 무관한 페이지로 연결됩니다. 주문 상세 페이지로 수정 후 재요청해 주세요.",
"reviewer": "카카오",
"at": "2026-07-25T15:01:10+09:00"
},
{
"status": "REQ",
"comment": "주문 완료 시 발송되는 정보성 메시지입니다.",
"reviewer": null,
"at": "2026-07-25T14:30:12+09:00"
}
],
"nextCursor": null
},
"meta": {
"requestId": "req_38ab90fe",
"quota": {
"rateLimit": {
"limit": 120,
"remaining": 119,
"resetAt": "2026-07-25T14:31:00+09:00"
}
}
}
}
HTTP/1.1 404 Not Found
{
"code": "4040",
"message": "템플릿을 찾을 수 없습니다: TPL_20260725149999",
"data": null,
"meta": {
"requestId": "req_f61b04d8",
"quota": {
"rateLimit": {
"limit": 120,
"remaining": 118,
"resetAt": "2026-07-25T14:31:00+09:00"
}
}
}
}
사전 정책 검증
/templates/validate
필요 스코프: template:read
템플릿을 등록하지 않고 반려 위험을 사전 진단합니다.
요청 바디는 템플릿 신청과
동일하며(autoReview는 무시), 검수 쿼터를 소모하지 않습니다.
Request Body
템플릿 신청과 동일 규격입니다.
autoReview는 무시됩니다.
Response
ERROR 위반이 없으면 true입니다. WARN만 있으면 true일 수 있습니다.
발견된 위반 목록입니다.
ERROR(반려 사유) 또는 WARN(심사 지연 가능).
위반 규칙 코드입니다 (예: AD_WORDING).
위반이 발견된 요청 필드입니다.
위반 내용 설명입니다.
curl -X POST "https://docs.almani-center.com/open/v1/templates/validate" \
-H "Authorization: Bearer ak_live_xxxx" \
-H "X-Site-Id: 1024" \
-H "Content-Type: application/json" \
-d '{
"templateName": "여름 특가 안내",
"templateType": "BA",
"category": "004001",
"content": "#{고객명}님, 지금 여름 특가 할인 중입니다!",
"buttons": [
{
"name": "지금 확인하기",
"type": "WL",
"urlMobile": "https://event.example.com/summer"
}
]
}'
// Java 17 + Spring Framework 6 RestClient
RestClient restClient = RestClient.builder()
.baseUrl("https://docs.almani-center.com/open/v1")
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer ak_live_xxxx")
.defaultHeader("X-Site-Id", "1024")
.build();
record TemplateButton(String name, String type, String urlMobile) {}
record ValidateRequest(
String templateName, String templateType, String category,
String content, List<TemplateButton> buttons) {}
var request = new ValidateRequest(
"여름 특가 안내", "BA", "004001",
"#{고객명}님, 지금 여름 특가 할인 중입니다!",
List.of(new TemplateButton("지금 확인하기", "WL",
"https://event.example.com/summer")));
String response = restClient.post()
.uri("/templates/validate")
.contentType(MediaType.APPLICATION_JSON)
.body(request)
.retrieve()
.body(String.class);
HTTP/1.1 200 OK
{
"code": "0000",
"message": "OK",
"data": {
"valid": false,
"violations": [
{
"severity": "ERROR",
"rule": "AD_WORDING",
"field": "content",
"message": "'특가', '할인' 등 광고성 표현은 정보성 알림톡에서 반려 사유입니다."
},
{
"severity": "WARN",
"rule": "BUTTON_URL_MISMATCH",
"field": "buttons[0].urlMobile",
"message": "버튼 URL 도메인이 사이트 도메인과 다릅니다. 심사 지연 가능성이 있습니다."
}
]
},
"meta": {
"requestId": "req_e5c31d78",
"quota": {
"rateLimit": {
"limit": 120,
"remaining": 118,
"resetAt": "2026-07-25T14:31:00+09:00"
}
}
}
}
HTTP/1.1 400 Bad Request
{
"code": "4003",
"message": "X-Site-Id 헤더가 누락되었습니다. GET /sites로 접근 가능한 사이트를 확인해 주세요.",
"data": null,
"meta": {
"requestId": "req_17c95ab0",
"quota": {
"rateLimit": {
"limit": 120,
"remaining": 117,
"resetAt": "2026-07-25T14:31:00+09:00"
}
}
}
}