# Open API

***

## **➊ API Authentication**

### **1-1. Enable OPEN API**&#x20;

1. Go to **\[Service Management] → \[Authentication]**.
2. Enable **Open API**.
3. When Open API is enabled, a **Service API Key** is automatically generated.
   * The API Key is used to authenticate API requests and encrypt transmitted data.
   * You can regenerate the API Key by clicking **Change API Key**.

***

### **1-2. Authorization Header**

{% hint style="danger" %}
Please make sure to set the following values in **every request header**.
{% endhint %}

{% hint style="info" %}

* If your service uses **Security Service features**, you can enable IP-based spam policies from\
  \&#xNAN;**\[Service Management] → \[Security Management] → \[Spam Management]**.

* When creating tickets via the **Open API**, if you include the **OC-Client-IP** value in the request header, spam detection will be performed based on the request’s IP address.
  {% endhint %}

* Authorization : Authentication string generated using the **Security Key**

* X-TC-Timestamp : Current UTC timestamp {new Date().getTime()}

* OUCODE : User Code (If not set, the default value is **Owner**)

***

### **1-3. Authorization Generation**

<figure><img src="/files/tPLrrCuIq5NBSV0Bjmzp" alt=""><figcaption></figcaption></figure>

{% stepper %}
{% step %}

#### **Generate the string according to the rules below.**

1. **Organization ID**
   * You can find this value on the \[Global Management] → \[Contract Service Status] → \[Organization Information] screen.
   * It is composed in a format similar to the following: `AbcdE1fghIj23K4x`
2. **API URI**
   * Enter the URI of the API you want to call. The URI must include the Service ID.
     * Example: `/yourService/openapi/v1/ticket.json`&#x20;
3. **Query Parameters**
   * If the API you are calling includes query parameters, sort the parameters by key name in alphabetical order first. Then concatenate only the parameter values using the `&` character. If there is only one parameter, do not include the `&` character. If there are no query parameters, this step can be omitted.
     * Example: Query parameter `1234`
       1. Sorted by key (alphabetical): `language=ko&page=1&pageSize=10`
       2. Concatenated values: `ko&1&10`
4. **Timestamp**
   * Apply the timestamp of the time when the API request is made. This value must be identical to the **X-TC-Timestamp** value sent in the request header.
     * Example: `1764031689401`
       {% endstep %}

{% step %}

#### **String Encryption**

* Encrypt the generated string using the **API Key** created in **1-1. Enable Open API** to generate the authentication key.
* The encryption method used is **HmacSHA256**.
  {% endstep %}

{% step %}

#### **Sending the Authentication Key**

* Set the generated authentication key as the value of the **Authorization** request header and send the request to Contiple.
* Contiple encrypts the received header and body values using the same API Key and compares the resulting Hash value.
* If the values match, the API request is processed successfully.
* If they do not match, an error code **400** is returned.
  {% endstep %}
  {% endstepper %}

***

### **1-4. JAVA Example**

#### **(1) Standard Request (GET)**

```java
// 유저 티켓 리스트
String URL = "http://nhn-cs.oc.nhncloud.com/APISimple/openapi/v1/ticket/enduser/usercode/list.json?categoryId=1&language=ko";
String organizationId = "WopqM8euoYw89B7i"; // 조직ID
String securityKey = "431402c0eaaf46d889f243db9e7492e2"; // 서비스 Key
String uri = "/APISimple/openapi/v1/ticket/enduser/usercode/list.json"; // request uri
long timestamp = new Date().getTime();
StringBuilder sb = new StringBuilder();
sb.append(organizationId);
sb.append(uri);
sb.append("1").append("&").append("ko"); // 매개변수 이름의 알파벳 순서에 따라(categoryId=1&language=ko)& 기호로 매개변수 값을 연결해 주세요 (1&ko)
sb.append(timestamp);// X-TC-Timestamp값과 동일

SecretKeySpec signingKey = new SecretKeySpec(securityKey.getBytes("UTF-8"), "HmacSHA256");
Mac mac = Mac.getInstance(signingKey.getAlgorithm());
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(sb.toString().getBytes("UTF-8"));
String authorization = new String(Base64.encodeBase64(rawHmac));

Request request = new Request.Builder().url(URL).get()
.header("Content-Type", "application/json")
.header("Authorization", authorization)
.header("X-TC-Timestamp", Long.toString(timestamp))
.build();
Call call = client.newCall(request);
Response response = call.execute();
```

#### **(2) Standard Request (POST)**

```java
// 티켓 생성
String URL = "http://nhn-cs.oc.nhncloud.com/APISimple/openapi/v1/ticket.json?language=ko";
String organizationId = "WopqM8euoYw89B7i"; // 조직ID
String securityKey = "431402c0eaaf46d889f243db9e7492e2"; // 서비스 Key
String uri = "/APISimple/openapi/v1/ticket.json"; // request uri
long timestamp = new Date().getTime();
StringBuilder sb = new StringBuilder();

String body = mapper.writeValueAsString(bodyContentObject);

sb.append(organizationId);
sb.append(uri);
sb.append("ko").append("&"); // 매개변수 이름의 알파벳 순서에 따라 & 기호로 매개변수 값을 연결해 주세요.
sb.append(body);// 매개변수 뒤에 body의 문자열 내용을 추가해 주세요.
sb.append(timestamp);// X-TC-Timestamp값과 동일

SecretKeySpec signingKey = new SecretKeySpec(securityKey.getBytes("UTF-8"), "HmacSHA256");
Mac mac = Mac.getInstance(signingKey.getAlgorithm());
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(sb.toString().getBytes("UTF-8"));
String authorization = new String(Base64.encodeBase64(rawHmac));

RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), body);

Request request = new Request.Builder().url(URL).post(body)
.header("Content-Type", "application/json")
.header("Authorization", authorization)
.header("X-TC-Timestamp", Long.toString(timestamp))
.header("OC-Client-IP", ip)
.build();

Call call = client.newCall(request);
Response response = call.execute();
```

#### **(3) File Upload**

```java
String URL = "http://nhn-cs.oc.nhncloud.com/APISimple/openapi/v1/ticket/attachments/upload.json";
String organizationId = "WopqM8euoYw89B7i"; // NHN Cloud 조직ID
String securityKey = "431402c0eaaf46d889f243db9e7492e2"; // 서비스 Key
String uri = "/APISimple/openapi/v1/ticket/attachments/upload.json"; // request uri
StringBuilder sb = new StringBuilder();
sb.append(organizationId);
sb.append(uri);
DigestUtils.appendMd5DigestAsHex(file.getInputStream(), sb);// 파일 첨부 시 , 파일의 MD5는 파라미터 값으로 인증 문자열에 추가
sb.append(new Date().getTime());// X-TC-Timestamp값과 동일
SecretKeySpec signingKey = new SecretKeySpec(securityKey.getBytes("UTF-8"), "HmacSHA256");
Mac mac = Mac.getInstance(signingKey.getAlgorithm());
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(sb.toString().getBytes("UTF-8"));
String authorization = new String(Base64.encodeBase64(rawHmac));
```

#### **(3) Contiple-side authentication method**

```java
// Generate authorization string sample with request
StringBuilder sb = new StringBuilder();
sb.append(org.getId()); // organization Id
sb.append(request.getRequestURI()); // request URI
// upload file
if (request instanceof MultipartHttpServletRequest) {
    MultipartHttpServletRequest mpRequest = (MultipartHttpServletRequest) request;
    MultipartFile file = mpRequest.getFile("file");
    DigestUtils.appendMd5DigestAsHex(file.getInputStream(), sb);
// other
} else {
    Map<String, String[]> params = request.getParameterMap();
    if (!params.isEmpty()) {
        // Sort parameter
        Map<String, String[]> treeMap = new TreeMap<>(params);
        for (Map.Entry<String, String[]> entry : treeMap.entrySet()) {
            sb.append(entry.getValue()[0]).append("&");
        }
        sb.deleteCharAt(sb.length() - 1); // Delete '&' character
    }   
    if (request instanceof BodyReaderHttpServletRequestWrapper) {
        BodyReaderHttpServletRequestWrapper requestWrapper = (BodyReaderHttpServletRequestWrapper) request;
        if (requestWrapper.hasBody()) {
            String body = new String(requestWrapper.getBody(), StandardCharsets.UTF_8);
            if (!params.isEmpty()) {
                // params is not empty, add '&' character
                sb.append("&");
            }
            sb.append(body);
        }
    }   
}
String time = request.getHeader("X-TC-Timestamp");
// No '&' character
sb.append(time);

return sb.toString();
```

* **If Contiple authentication fails, the result is returned as shown below.**
* **(Please verify that the Authorization string is encrypted using the correct method.)**

```json
{
    "header": {
        "resultCode": 400,
        "resultMessage": "무효한 요청입니다.",
        "isSuccessful": false
    },
    "result": null
}
```

* **Authentication failure condition:**

> **400**
>
> 1. Authorization is blank
>
> 2. X-TC-Timestamp is not numeric
>
> 3. X-TC-Timestamp is expired (valid within 5 minutes)
>
> 4. Multipart request but file is null
>
> 5. Authorization is incorrect
>
> **403**
>
> 1. securityKey is null
> 2. clientIp is not allowed

***

## **➋ Common Return Results**

### **2-1. Return Result Example**

```json
//성공: 상세
{
    "header": {
        "resultCode": 200,
        "resultMessage": "",
        "isSuccessful": true
    },
    "result": {
        "content": {
        }
    }
}

//성공: 리스트
{
    "header": {
        "resultCode": 200,
        "resultMessage": "",
        "isSuccessful": true
    },
    "result": {
        "contents": [{
        }]
    }
}

//실패
{
    "header": {
        "resultCode": 403,
        "resultMessage": "Access Denied",
        "isSuccessful": false
    },
    "result": null
}
```

***

### **2-2. Return Result Description**

<table><thead><tr><th width="132">명칭</th><th width="173">변수</th><th width="119">데이터 타입</th><th>설명</th></tr></thead><tbody><tr><td><strong>Header</strong></td><td>resultCode</td><td>Integer</td><td>Return result code, <code>200</code> indicates a successful request.</td></tr><tr><td></td><td>resultMessage</td><td>String</td><td>Return error message</td></tr><tr><td></td><td>isSuccessful</td><td>Boolean</td><td>Execution result (Sucess: <code>true</code>, Failure: <code>false</code>) </td></tr><tr><td><strong>Result</strong></td><td>contents</td><td>JSON</td><td>List result content</td></tr><tr><td></td><td>content</td><td>JSON</td><td>Detail result content</td></tr></tbody></table>

***

### **2-3. Return Code**

> * 200 : SUCCESS
> * 400 : Bad Request
> * 403 : Access Denied(Forbidden)
> * 404 : Not Data Found
> * 500 : Server Error
> * 9007 : The related data already exists.
> * 9005 : No related data exists.
> * 1001 : The number of inquiries has exceeded the limit. Please contact us again after a while.
>   * The following cases apply when **\[Spam Management] → \[Spam Inquiry Blocking]** is enabled:
>   * If inquiry creation is attempted 3 or more times within 1 minute from the same IP, ticket creation is blocked for 24 hours.
> * 1002 : The number of inquiries has exceeded the limit. Please contact us again after a while.
>   * The following cases apply when **\[Spam Management] → \[Spam Inquiry Blocking]** is enabled:
>   * If inquiry creation is attempted 10 or more times within 24 hours from the same IP, ticket creation is blocked for 24 hours.

***

### **2-4. Return Code Details (Failure)**

> **400**
>
> 1. Authorization is blank
>
> 2. X-TC-Timestamp is not numeric
>
> 3. X-TC-Timestamp is expired (valid within 5 minutes)
>
> 4. Multipart request but file is null
>
> 5. Authorization is incorrect
>
> **403**
>
> 1. securityKey is null
> 2. clientIp is not allowed

***

## **➌ API List**

### **3-1. Development Environment URL**

<table><thead><tr><th width="159">환경</th><th>BaseUrl</th></tr></thead><tbody><tr><td>Alpha</td><td>https://{domain}.oc.alpha-nhncloud.com</td></tr><tr><td>Beta</td><td>https://{domain}.oc.beta-nhncloud.com</td></tr><tr><td>Real</td><td>https://{domain}.oc.nhncloud.com</td></tr></tbody></table>

***

### **3-2. Security Key URL**

<table><thead><tr><th width="276">Security Key</th><th>URL</th></tr></thead><tbody><tr><td>Security Key of Service</td><td>/{serviceId}/openapi/v1/*</td></tr><tr><td>Available for direct use without authentication</td><td>/{serviceId}/api/v2/*</td></tr></tbody></table>

***

### **3-3. API List**

<table><thead><tr><th width="122">그룹</th><th width="177">명칭</th><th width="77">유형</th><th>URL</th><th>설명</th></tr></thead><tbody><tr><td><strong>Service</strong></td><td>Service Details</td><td>GET</td><td>/{serviceId}/api/v2/service.json</td><td>Retrieve service information using the Service ID</td></tr><tr><td><strong>Notice</strong></td><td>Heading List</td><td>GET</td><td>/{serviceId}/api/v2/notice/categories.json</td><td>Retrieve the list of notice headings</td></tr><tr><td></td><td>Tag List</td><td>GET</td><td>/{serviceId}/api/v2/notice/tags.json</td><td>Retrieve the list of notice tags</td></tr><tr><td></td><td>Notice List</td><td>GET</td><td>/{serviceId}/api/v2/notice/list.json</td><td>Retrieve the Help Center notice list</td></tr><tr><td></td><td>Notice Details</td><td>GET</td><td>/{serviceId}/api/v2/notice/detail/{id}.json</td><td>Retrieve notice content using the Notice ID</td></tr><tr><td></td><td>Open &#x26; Download Notice Attachments</td><td>GET</td><td>/{serviceId}/api/v2/notice/attachments/{id}</td><td>Open or download notice attachments</td></tr><tr><td><strong>FAQ</strong></td><td>Category List</td><td>GET</td><td>/{serviceId}/api/v2/helpdoc/categories.json</td><td>Retrieve the FAQ category list</td></tr><tr><td></td><td>FAQ List</td><td>GET</td><td>/{serviceId}/api/v2/helpdoc/list.json</td><td>Retrieve the Help Center FAQ list</td></tr><tr><td></td><td>FAQ Detail</td><td>GET</td><td>/{serviceId}/api/v2/helpdoc/detail/{id}.json</td><td>Retrieve FAQ content using the FAQ ID</td></tr><tr><td></td><td>Open &#x26; Download FAQ Attachments</td><td>GET</td><td>/{serviceId}/api/v2/helpdoc/attachments/{id}</td><td>Open or download FAQ attachments</td></tr><tr><td><strong>Submit Inquiry</strong></td><td>Inquiry Type List</td><td>GET</td><td>/{serviceId}/api/v2/ticket/categories.json</td><td>Retrieve the list of inquiry types within the service</td></tr><tr><td></td><td>Inquiry Type Field List</td><td>GET</td><td>/{serviceId}/api/v2/ticket/field/user/{categoryId}.json</td><td>Retrieve the list of fields associated with a specific inquiry type</td></tr><tr><td></td><td>Ticket Attachment Upload</td><td>POST</td><td>/{serviceId}/openapi/v1/ticket/attachments/upload.json</td><td>Upload files to the server</td></tr><tr><td></td><td>Create Ticket</td><td>POST</td><td>/{serviceId}/openapi/v1/ticket.json</td><td>Create a new ticket</td></tr><tr><td><strong>Inquiry History</strong></td><td>Customer Ticket List</td><td>GET</td><td>/{serviceId}/openapi/v1/ticket/enduser/{usercode}/list.json</td><td>Display a customer’s ticket list based on search conditions</td></tr><tr><td></td><td>Ticket Details</td><td>GET</td><td>/{serviceId}/openapi/v1/ticket/enduser/{usercode}/{ticketId}/detail.json</td><td>Retrieve details of a ticket submitted by the customer</td></tr><tr><td></td><td>Open &#x26; Download Ticket Attachments</td><td>GET</td><td>/{serviceId}/api/v2/ticket/attachments/{id}</td><td>Open or download ticket attachments</td></tr><tr><td></td><td>Customer Follow-up Inquiry</td><td>POST</td><td>/{serviceId}/openapi/v1/ticket/enduser/{usercode}/{ticketId}/comment.json</td><td>Submit a follow-up inquiry based on the Ticket ID</td></tr></tbody></table>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.contiple.com/eng/api-guide/open-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
