템플릿 API

알림톡 템플릿을 등록하고 검수를 요청·관리하는 API입니다. 템플릿은 카카오 심사를 통과(승인)한 뒤에만 발송에 사용할 수 있으며, 심사는 카카오가 수행하고 알마니가 상태를 동기화해 제공합니다.

템플릿 상태

일반적인 흐름은 REGREQAPR 또는 REJ 이며, 반려된 템플릿은 수정 후 재검수 요청으로 다시 REQ 상태가 됩니다.

상태의미설명
REG 등록 검수 요청 전 초안 상태입니다. autoReview: false로 등록하거나 검수 요청을 취소하면 이 상태가 됩니다.
REQ 검수중 카카오 심사가 진행 중입니다.
APR 승인 심사를 통과했습니다. 발송에 사용할 수 있습니다(sendable: true).
REJ 반려 심사에서 반려되었습니다. rejectReason으로 사유를 확인하고 수정 후 재검수를 요청하세요.
UPT 수정중 템플릿을 수정하고 있는 상태입니다. 재검수 요청 대상입니다.
시작 전에 Base URL은 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에는 분당 요청 한도가 항상 포함됩니다. 에러 응답 형식과 전체 에러 코드는 에러 형식과 코드를 참고하세요.

개인화 변수 카탈로그

GET /variables

필요 스코프: template:read

템플릿 변수 매핑(mappingData)에 사용할 수 있는 개인화 변수 목록을 반환합니다. 사이트의 플랫폼 기준으로 사용 가능한 변수만 반환하며, triggerEvent를 지정하면 해당 트리거가 제공하는 변수만 조회합니다.

개인화 변수란? 트리거 이벤트(주문완료, 배송시작 등)가 실어오는 수신자별 값을 알마니가 한글 표준명으로 표준화한 변수입니다 — 고객명, 주문번호, 결제금액, 송장번호, 쿠폰명 등. 템플릿 본문·버튼 URL에는 #{변수명} 형식으로 사용합니다. 자세한 개념은 개인화 변수 가이드를 참고하세요.

Query 파라미터

triggerEvent 선택 string

트리거 값입니다 (예: orderComplete, deliveryStart). 미지정 시 전체 변수를 반환합니다.

Response

variables[] array of object

사용 가능한 개인화 변수 목록입니다.

variables[].name string

개인화 변수명(한글 표준명)입니다. 매핑과 #{변수명}에 이 이름을 사용합니다.

variables[].group string

변수 그룹입니다 (예: 고객, 주문, 배송).

variables[].description string

변수 설명입니다.

variables[].triggers array of string

이 변수를 제공하는 트리거 목록입니다.

요청 예시
# 트리거별 필터: /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"
      }
    }
  }
}

템플릿 신청

POST /templates

필요 스코프: template:write

템플릿을 등록하고, 기본값으로는 즉시 카카오 검수까지 요청합니다. 성공 시 templateCode가 발급되고 상태는 REQ가 됩니다. autoReview: false를 보내면 REG 초안으로만 저장됩니다.

Request Body

templateName 필수 string

템플릿 이름입니다. 최대 30자, 사이트 내 중복 불가(중복 시 4091).

templateType 필수 string

템플릿 유형입니다. BA 기본형 · IM 이미지형 · EX 강조표기형 · IT 아이템리스트형.

category 필수 string

알림톡 템플릿 카테고리 코드입니다.

content 필수 string

메시지 본문입니다. 변수는 #{변수명} 형식으로 넣습니다. 이미지형은 최대 400자, 그 외 유형은 최대 1,000자입니다. 변수만으로 구성된 본문은 반려됩니다.

securityEnabled 선택 boolean

보안 템플릿 여부입니다(메인 기기 외 노출 제한). 기본 false.

comments 선택 string

검수 요청 의견입니다. 최대 500자, 카카오 심사 담당자에게 전달됩니다.

buttons 선택 array of object

버튼 목록입니다. 최대 5개. CTA 랜딩은 WL(웹링크)을 사용합니다.

buttons[].name 필수 string

버튼 문구입니다. 최대 14자.

buttons[].type 필수 string

버튼 유형입니다. WL 웹링크 · AL 앱링크 · DS 배송조회 · BK 봇키워드 · MD 메시지전달 · AC 채널추가.

buttons[].urlMobile WL일 때 필수 string

모바일 랜딩 URL입니다(https). 변수를 포함할 수 있습니다.

buttons[].urlPc 선택 string

PC 랜딩 URL입니다.

buttons[].androidScheme / iosScheme AL일 때 필수 string

앱 스킴입니다.

emphasis EX일 때 필수 object

강조표기형 요소입니다 — { "title": "강조 타이틀(변수 가능)", "subtitle": "보조 문구" }.

image IM일 때 필수 object

이미지형 요소입니다 — { "imageUrl": "https://…" }. 사전 업로드된 이미지 URL을 사용합니다(권장 규격 800×400).

itemList IT일 때 필수 object

아이템리스트형 요소입니다 — { "header", "items": [{"title","description"}](2~10개), "highlight": {"title","description"} }.

mappingData 선택 object

템플릿 변수 → 개인화 변수 매핑입니다. 템플릿 변수명이 표준 개인화 변수명과 같으면 생략할 수 있습니다(자동 매핑). 이름이 다를 때만 명시합니다.

autoReview 선택 boolean

기본 true — 등록 즉시 검수를 요청합니다. falseREG 초안으로만 저장합니다.

usedFor 선택 string

대상 트리거 값입니다. 지정하면 해당 트리거가 제공하는 개인화 변수를 기준으로 mappingData를 검사합니다.

매핑 검증 신청 시점에 매핑 대상 개인화 변수가 표준 카탈로그 (GET /variables)에 없으면 4000 에러와 함께 사용할 수 없는 매핑 대상을 안내합니다. usedFor(대상 트리거)를 함께 보내면 해당 트리거가 제공하는 변수 기준으로 더 엄격히 검사합니다. 본문의 #{변수} 중 매핑(자동 매핑 포함)이 해석되지 않는 것이 있으면 응답의 data.unmappedVariables로 알려주며, 발송 연결 전까지 매핑을 완성해야 합니다. 발송 시점에 매핑 값이 비어 있는 수신자는 발송을 건너뜁니다. 이 템플릿을 발송에 연결하는 작업(캠페인 설정)은 알마니 콘솔에서 수행하며, 그때 이 매핑이 기본값으로 상속됩니다.

Response

templateId number

숫자 ID입니다(참고용).

templateCode string

알마니가 발급하는 TPL_ 접두사의 불투명한 템플릿 코드입니다. 이후 모든 조회·검수 요청의 키로 사용합니다.

approveStatus string

REQ(autoReview) 또는 REG.

requestedAt string | null

autoReview: true일 때의 검수 요청 시각입니다. 초안 생성 시 null입니다.

unmappedVariables array of string

해석되지 않은 개인화 변수가 있을 때만 포함됩니다.

주요 에러

코드HTTP설명
4091409템플릿명 중복
4291429일일 템플릿 검수 요청 한도 초과
6001502 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"
      }
    }
  }
}

템플릿 목록 조회

GET /templates

필요 스코프: template:read

사이트의 사용자 생성 템플릿 중 사용 중이며 삭제되지 않은 목록을 조회합니다. 기본 제공 템플릿은 포함하지 않습니다. 상태 필터와 커서 페이지네이션(공통 규약 참조)을 지원합니다.

Query 파라미터

status 선택 string

상태 필터입니다 — REG|REQ|APR|REJ|UPT. 복수 상태는 콤마로 구분합니다 (예: status=REQ,REJ).

keyword 선택 string

템플릿명 또는 템플릿 코드 부분 일치 검색입니다.

limit 선택 int

페이지 크기입니다. 기본 20, 최대 100.

cursor 선택 string

이전 응답의 data.nextCursor 값입니다. 없으면 첫 페이지를 반환합니다.

Response

items[].templateCode string

템플릿 코드입니다.

items[].templateName string

템플릿 이름입니다.

items[].approveStatus string

REG/REQ/APR/REJ/UPT.

items[].templateType string

BA/IM/EX/IT.

items[].rejectReason string | null

가장 최근 반려 사유입니다. 반려 이력이 없으면 null입니다.

items[].inUse boolean

발송에 연결되어 사용 중인 템플릿인지 여부입니다.

items[].createdAt / updatedAt string / string | null

생성 / 수정 시각입니다.

nextCursor string | 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"
      }
    }
  }
}

템플릿 조회

GET /templates/{templateCode}

필요 스코프: template:read

템플릿 단건 상세와 AL_TEMPLATE.APPROVE_STATUS에 저장된 검수 상태를 조회합니다. 상태가 REQ여도 요청 처리 중 비즈톡 상태를 실시간으로 재조회하지 않습니다.

Path 파라미터

templateCode 필수 string

템플릿 코드입니다 (예: TPL_20260725143012).

Response

templateCode / templateName string

템플릿 코드 / 이름입니다.

approveStatus string

현재 상태입니다.

rejectReason string | null

가장 최근 카카오 반려 코멘트 전문입니다. 반려 이력이 없으면 null입니다.

templateType / category / securityEnabled

등록 시 지정한 값입니다.

content string

본문 원문입니다.

buttons[] array

등록된 버튼입니다 (name/type/urlMobile/urlPc …).

emphasis / image / itemList object | null

유형별 부가 요소입니다.

mappingData object | null

변수 매핑입니다.

sendable boolean

APR 여부 — 발송에 사용할 수 있는지를 나타냅니다.

recentHistory[] array

최근 검수 이력 5건입니다 — { status, comment, reviewer, at }. 전체 이력은 검수 이력으로 조회합니다.

주요 에러

코드HTTP설명
4040404템플릿 코드에 해당하는 템플릿 없음
참고 우측 응답 예시는 반려(REJ) 케이스입니다. 반려 사유를 반영해 수정한 뒤 재검수 요청을 보내세요.
요청 예시
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"
      }
    }
  }
}

재검수 요청

POST /templates/{templateCode}/review

필요 스코프: template:write

반려(REJ) · 초안(REG) · 수정중(UPT) 상태 템플릿의 검수를 (재)요청합니다. 그 외 상태에서 호출하면 4090 에러가 반환됩니다. 요청 바디에 수정할 필드를 함께 보내면 수정과 재검수를 한 번에 처리합니다.

Path 파라미터

templateCode 필수 string

템플릿 코드입니다.

Request Body

content / buttons / emphasis / image / itemList / mappingData 선택

템플릿 신청과 동일 규격입니다. 보낸 필드만 수정한 뒤 검수를 요청합니다.

comments 선택 string

카카오 심사 담당자에게 전달되는 의견입니다. 반려 사유에 대한 소명을 권장합니다.

Response

templateCode string

템플릿 코드입니다.

approveStatus string

성공 시 REQ가 됩니다.

requestedAt string

검수 요청 시각입니다.

주요 에러

코드HTTP설명
4090409 상태 전이 불가 — 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"
      }
    }
  }
}

검수 요청 취소

DELETE /templates/{templateCode}/review

필요 스코프: template:write

REQ 상태의 검수 요청을 취소합니다. 성공 시 상태는 REG로 되돌아갑니다. 카카오 심사가 이미 시작된 뒤에는 취소가 거부될 수 있으며, 이때 6000 에러로 사유를 전달합니다.

Path 파라미터

templateCode 필수 string

템플릿 코드입니다.

Response

templateCode string

템플릿 코드입니다.

approveStatus string

성공 시 REG로 복귀합니다.

주요 에러

코드HTTP설명
6000502심사 착수 후 취소 거부 (사유 포함)
요청 예시
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"
      }
    }
  }
}

검수 이력

GET /templates/{templateCode}/review-history

필요 스코프: template:read

템플릿의 전체 검수 이력을 반환합니다. 커서 페이지네이션(공통 규약 참조)을 지원합니다.

Path 파라미터

templateCode 필수 string

템플릿 코드입니다.

Query 파라미터

limit 선택 int

페이지 크기입니다. 기본 20, 최대 100.

cursor 선택 string

이전 응답의 data.nextCursor 값입니다.

Response

items[].status string

REG | REQ | APR | REJ | UPT.

items[].comment string | null

검수 요청 의견 또는 카카오 심사 코멘트입니다.

items[].reviewer string | null

코멘트 작성 주체입니다.

items[].at string | null

이력 발생 시각입니다.

nextCursor string | null

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"
      }
    }
  }
}

사전 정책 검증

POST /templates/validate

필요 스코프: template:read

템플릿을 등록하지 않고 반려 위험을 사전 진단합니다. 요청 바디는 템플릿 신청과 동일하며(autoReview는 무시), 검수 쿼터를 소모하지 않습니다.

권장 사용법 템플릿 신청 전에 이 API로 먼저 검증하면 반려로 인한 재작업과 일일 검수 한도 소모를 줄일 수 있습니다. 반려 기준은 템플릿 심사 가이드를 참고하세요.

Request Body

templateName / templateType / category / content / …

템플릿 신청과 동일 규격입니다. autoReview는 무시됩니다.

Response

valid boolean

ERROR 위반이 없으면 true입니다. WARN만 있으면 true일 수 있습니다.

violations[] array of object

발견된 위반 목록입니다.

violations[].severity string

ERROR(반려 사유) 또는 WARN(심사 지연 가능).

violations[].rule string

위반 규칙 코드입니다 (예: AD_WORDING).

violations[].field string

위반이 발견된 요청 필드입니다.

violations[].message string

위반 내용 설명입니다.

요청 예시
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"
      }
    }
  }
}