KEMENPAR Tourism Business Verification API
This API provides verification services for Business Identification Numbers (NIB) and KBLI codes within the tourism sector through integration with the Online Single Submission (OSS) system.
System Architecture
OTA Platform → KEMENPAR API → OSS Validation System
OTA platforms send a POST request to the KEMENPAR API to validate whether a submitted NIB and its associated KBLI (Indonesian Standard Industrial Classification) are registered and compliant within the OSS system.
Security Features
The API implements multiple security layers to ensure secure communication, request authenticity, and protection against abuse.
Supported Security Mechanisms
-
Transport Layer Security (TLS 1.2 or higher)
All API communications must use HTTPS encryption. -
API Credential Authentication
Each OTA partner receives unique API credentials for secure access. -
HMAC SHA256 Signature Validation
Every request must include a cryptographic signature generated using the providedclient_secret. -
Rate Limiting
Requests are limited to 30 requests per minute per client. -
IP Whitelisting (Production Environment Only)
Only registered public IP addresses are allowed to access the Production API.
Authentication
The KEMENPAR API uses HMAC SHA256 signature-based authentication to validate all incoming requests securely.
All OTA partners and third-party systems must generate a valid request signature before accessing the API endpoint.
Each authorized OTA developer team will receive the following credentials from KEMENPAR:
X-cons-idX-user-keyclient_secret
These credentials are required for all API requests.
Signature Generation
The X-signature header must be generated using the provided client_secret.
Signature Formula
stringToSign = X-cons-id + "&" + X-timestamp
Example
SBX_AIRBNB_7RT4D&1779075264
Base URL
Sandbox Environment
https://api-industri.kemenpar.tech
Production Environment
https://api-industri.kemenpar.go.id
Endpoint Format
Sandbox
/api/{ota}/sandbox/v1/verify-nib
Production
/api/{ota}/v1/verify-nib
Supported OTA Values
| OTA Platform | OTA Code |
|---|---|
| Airbnb | airbnb |
| Agoda | agoda |
| Booking.com | booking |
| Expedia | expedia |
| OYO | oyo |
| RedDoorz | reddoorz |
| Traveloka | traveloka |
| Tiket.com | tiket |
| Trip.com | trip |
Authentication Flow
Every API request MUST follow the steps below:
-
Retrieve API credentials provided by KEMENPAR:
X-cons-idX-user-keyclient_secret
-
Generate a UTC Unix Timestamp.
-
Create the signature string:
Note: All timestamps MUST use UTC timezone.
cons_id×tamp
Example:
SBX_AIRBNB_7RT4D&1779075264
-
Generate an HMAC SHA256 hash using
client_secret. -
Encode the generated hash using Base64.
-
Send all required headers in the request.
Signature Specification
All API requests must include a valid digital signature for authentication and request validation.
The signature is generated using the Consumer ID (X-cons-id) combined with the request timestamp (X-timestamp). The generated value must be sent in the X-signature request header.
The signature ensures that every request comes from an authorized client and prevents unauthorized API access.
Signature String
cons_id×tamp
Hash Algorithm
HMAC SHA256
Encoding
Base64
Signature Generation
The X-signature header must be generated using the provided client_secret.
Signature Formula
stringToSign = X-cons-id + "&" + X-timestamp
Postman Environment Setup
Create a Postman Environment with the following variables:
| Variable | Description |
|---|---|
client_secret |
Secret key used for signature generation |
Required Headers
Create a Postman Headers with the following variables:
| Header | Required | Description |
|---|---|---|
Content-Type |
YES | Request content type (application/json) |
X-cons-id |
YES | Consumer ID provided by KEMENPAR |
X-user-key |
YES | API user key provided by KEMENPAR |
cURL Example (Sandbox)
Use the following cURL command to test the API or import it directly into Postman.
curl --location 'https://api-industri.kemenpar.tech/api/airbnb/sandbox/v1/verify-nib' \
--header 'Content-Type: application/json' \
--header 'X-cons-id: SBX_BOOKING_9XQ2L' \
--header 'X-user-key: sbxBk9Qf71mNxL0vUtW2cD8aPqYeRjHsF6AiKzMp' \
--data '{
"nib": "9120205342672",
"kbli": "55110",
"nku": "202112011258206434761"
}'
Sample Request Body
{
"nib": "3012210012345",
"kbli": "55110",
"nku": "202112051258206434567"
}
cURL Example (Production)
Use the following cURL command to the API or import it directly into Postman.
curl --location 'https://api-industri.kemenpar.go.id/api/airbnb/v1/verify-nib' \
--header 'Content-Type: application/json' \
--header 'X-cons-id: SBX_BOOKING_9XQ2L' \
--header 'X-user-key: sbxBk9Qf71mNxL0vUtW2cD8aPqYeRjHsF6AiKzMp' \
--data '{
"nib": "9120205342672",
"kbli": "55110",
"nku": "202112011258206434761"
}'
Sample Request Body
{
"nib": "3012210012345",
"kbli": "55110",
"nku": "202112051258206434567"
}
Postman Pre-request Script
Paste the following script into the Postman Pre-request Script tab.
// ===== LOAD HEADER & ENVIRONMENT VARIABLES =====
const cons_id = pm.request.headers.get("X-cons-id");
const user_key = pm.request.headers.get("X-user-key");
const secret = pm.environment.get("client_secret");
const timestamp = Math.floor(Date.now() / 1000);
// ===== SIGNATURE =====
const stringToSign = cons_id + "&" + timestamp;
const hash = CryptoJS.HmacSHA256(stringToSign, secret);
const signature = CryptoJS.enc.Base64.stringify(hash);
// ===== SET to ENV =====
pm.environment.set("X-timestamp", timestamp);
pm.environment.set("X-signature", signature);
// ===== SET HEADER OTOMATIS (INI PENTING 🔥) =====
pm.request.headers.upsert({ key: "X-cons-id", value: cons_id });
pm.request.headers.upsert({ key: "X-user-key", value: user_key });
pm.request.headers.upsert({ key: "X-timestamp", value: timestamp.toString() });
pm.request.headers.upsert({ key: "X-signature", value: signature });
pm.request.headers.upsert({ key: "Content-Type", value: "application/json" });
// DEBUG
console.log("X-timestamp:", timestamp);
console.log("X-signature:", signature);
Example Success Response
{
"code": 200,
"message": "Success",
"status": true,
"data": {
"nib": "3012210013123",
"kbli": "55110",
"nku": "R-202112051258206434567"
}
}
Example Error Response
{
"code": 401,
"message": "Invalid Signature",
"status": false
}
Multi-language Signature Script
PHP
<?php
$consId = 'YOUR_CONS_ID';
$clientSecret = 'YOUR_CLIENT_SECRET';
$userKey = 'YOUR_USER_KEY';
// Generate UNIX UTC timestamp
$timestamp = (string) time();
// Create signature string
$stringToSign = $consId . '&' . $timestamp;
// Generate HMAC SHA256 + Base64 signature
$signature = base64_encode(
hash_hmac(
'sha256',
$stringToSign,
$clientSecret,
true
)
);
// Request headers
$headers = [
'Content-Type: application/json',
'X-cons-id: ' . $consId,
'X-user-key: ' . $userKey,
'X-timestamp: ' . $timestamp,
'X-signature: ' . $signature,
];
print_r($headers);
JavaScript
const crypto = require('crypto');
const consId = 'YOUR_CONS_ID';
const clientSecret = 'YOUR_CLIENT_SECRET';
const userKey = 'YOUR_USER_KEY';
// Generate UNIX UTC timestamp
const timestamp = Math.floor(Date.now() / 1000).toString();
// Create signature string
const stringToSign = consId + '&' + timestamp;
// Generate HMAC SHA256 + Base64 signature
const signature = crypto
.createHmac(
'sha256',
clientSecret
)
.update(stringToSign)
.digest('base64');
// Request headers
const headers = {
'Content-Type': 'application/json',
'X-cons-id': consId,
'X-user-key': userKey,
'X-timestamp': timestamp,
'X-signature': signature
};
console.log(headers);
Python
import time
import hmac
import hashlib
import base64
cons_id = "YOUR_CONS_ID"
client_secret = "YOUR_CLIENT_SECRET"
user_key = "YOUR_USER_KEY"
# Generate UNIX UTC timestamp
timestamp = str(
int(time.time())
)
# Create signature string
string_to_sign = (
cons_id +
"&" +
timestamp
)
# Generate HMAC SHA256 + Base64 signature
signature = base64.b64encode(
hmac.new(
client_secret.encode("utf-8"),
string_to_sign.encode("utf-8"),
hashlib.sha256
).digest()
).decode("utf-8")
# Request headers
headers = {
"Content-Type": "application/json",
"X-cons-id": cons_id,
"X-user-key": user_key,
"X-timestamp": timestamp,
"X-signature": signature
}
print(headers)
Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class SignatureGenerator {
public static void main(String[] args)
throws Exception {
String consId = "FILL_CONS_ID";
String secretKey = "FILL_SECRET";
String userKey = "FILL_USER_KEY";
// Generate Unix timestamp (UTC)
String timestamp = String.valueOf(
System.currentTimeMillis() / 1000
);
// Create signature string
String stringToSign =
consId + "&" + timestamp;
// Generate HMAC SHA256
Mac sha256Hmac = Mac.getInstance(
"HmacSHA256"
);
SecretKeySpec secretKeySpec =
new SecretKeySpec(
secretKey.getBytes(),
"HmacSHA256"
);
sha256Hmac.init(secretKeySpec);
// Generate Base64 Signature
String signature =
Base64.getEncoder().encodeToString(
sha256Hmac.doFinal(
stringToSign.getBytes()
)
);
// Request Headers
Map<String, String> headers =
new HashMap<>();
headers.put("X-cons-id", consId);
headers.put("X-user-key", userKey);
headers.put("X-timestamp", timestamp);
headers.put("X-signature", signature);
headers.put("Content-Type", "application/json");
System.out.println(headers);
}
}
C
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
class Program
{
static void Main()
{
string consId = "FILL_CONS_ID";
string secretKey = "FILL_SECRET";
string userKey = "FILL_USER_KEY";
// Generate Unix timestamp (UTC)
string timestamp =
DateTimeOffset.UtcNow
.ToUnixTimeSeconds()
.ToString();
// Create signature string
string stringToSign =
consId + "&" + timestamp;
// Generate HMAC SHA256 signature
string signature;
using (
HMACSHA256 hmac =
new HMACSHA256(
Encoding.UTF8.GetBytes(secretKey)
)
)
{
byte[] hash =
hmac.ComputeHash(
Encoding.UTF8.GetBytes(stringToSign)
);
signature =
Convert.ToBase64String(hash);
}
// Request headers
var headers =
new Dictionary<string, string>
{
{ "X-cons-id", consId },
{ "X-user-key", userKey },
{ "X-timestamp", timestamp },
{ "X-signature", signature },
{ "Content-Type", "application/json" }
};
// Print headers
foreach (var header in headers)
{
Console.WriteLine(
$"{header.Key}: {header.Value}"
);
}
}
}
HTTP Response Codes
| Condition | HTTP Code | Status | Message |
|---|---|---|---|
| NIB, KBLI, and NKU match successfully | 200 |
true |
Success |
| NIB not found | 200 |
false |
NIB not found |
| KBLI does not match | 200 |
false |
KBLI does not match |
| NKU does not match | 200 |
false |
NKU does not match |
| Missing required fields | 422 |
false |
Validation Error |
| Invalid JSON format | 400 |
false |
Bad Request |
| Invalid signature or authentication failed | 401 |
false |
Unauthorized / Invalid Signature |
| IP address is not whitelisted | 403 |
false |
IP Address Not Allowed |
| Invalid API endpoint URL | 404 |
false |
Endpoint Not Found |
| Request limit exceeded | 429 |
false |
Too Many Requests |
| Server, database, or OSS service unavailable | 500 |
false |
Internal Server Error |
Security Notes
- All timestamps MUST use UTC Unix Timestamp format.
- Signatures are unique for every request.
- Maximum timestamp tolerance is ±5 minutes.
- All API communication MUST use HTTPS.
- API credentials MUST only be stored on backend servers.
- Credentials MUST NOT be exposed publicly.
- Credentials MUST NOT be stored in frontend or mobile applications.
- Requests with invalid signatures will be rejected automatically.
- Replay attack protection is enforced through timestamp validation.
- Signature comparison is case-sensitive.
IP Whitelisting (Production Only)
Production API access is protected using IP whitelisting.
All OTA partners must register their static public IP addresses before accessing the Production environment.
Requests originating from unregistered IP addresses will be rejected automatically.
Requirements
- Static Public IP Address
- Registered and approved by KEMENPAR
- Multiple IP addresses are supported
Important Notes
- IP whitelisting is NOT required in the Sandbox environment.
- Dynamic IP addresses are NOT recommended.
- VPN or rotating IP addresses may cause request rejection.
Example Response
{
"success": {
"code": 200,
"message": "Success",
"status": true
},
"nib_not_found": {
"code": 200,
"message": "NIB not found",
"status": false
},
"kbli_not_match": {
"code": 200,
"message": "KBLI does not match",
"status": false
},
"nku_not_match": {
"code": 200,
"message": "NKU does not match",
"status": false
},
"validation_error": {
"code": 422,
"message": "Validation Error",
"status": false
},
"bad_request": {
"code": 400,
"message": "Bad Request",
"status": false
},
"invalid_signature": {
"code": 401,
"message": "Unauthorized / Invalid Signature",
"status": false
},
"ip_not_allowed": {
"code": 403,
"message": "IP Address Not Allowed",
"status": false
},
"endpoint_not_found": {
"code": 404,
"message": "Endpoint Not Found",
"status": false
},
"too_many_requests": {
"code": 429,
"message": "Too Many Requests",
"status": false
},
"internal_server_error": {
"code": 500,
"message": "Internal Server Error",
"status": false
}
}
Endpoints
POST api/{ota}/sandbox/v1/verify-nib
Example request:
curl --request POST \
"http://localhost/api/consequatur/sandbox/v1/verify-nib" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"nib\": 11613.31890586,
\"kbli\": 11613.31890586,
\"nku\": \"consequatur\"
}"
const url = new URL(
"http://localhost/api/consequatur/sandbox/v1/verify-nib"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"nib": 11613.31890586,
"kbli": 11613.31890586,
"nku": "consequatur"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
POST api/{ota}/v1/verify-nib
Example request:
curl --request POST \
"http://localhost/api/consequatur/v1/verify-nib" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"nib\": \"consequatur\",
\"kbli\": \"consequatur\",
\"id_proyek\": \"consequatur\"
}"
const url = new URL(
"http://localhost/api/consequatur/v1/verify-nib"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"nib": "consequatur",
"kbli": "consequatur",
"id_proyek": "consequatur"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.