Download OpenAPI specification:Download
EDH REST APIs for retrieving licence data.
Note - this specification is subject to changes based on evolution of the APIs.
The RESTful API adopts Semantic Versioning 2.0.0 for releases, and every new release of the API increments the version numbers in the following format:
{MAJOR}.{MINOR}.{PATCH}{MAJOR} number introduces incompatible API changes with previous {MAJOR} number also resets {MINOR} to 0,{MINOR} number introduces new functionalities or information that are backward compatible also resets {PATCH} to 0, and{PATCH} number introduces bug fixes and remains backward compatible.Pre-release or draft versions, when provided, are denoted by appended hypen - with a series of separate identifiers {LABEL}-{VERSION} following the {PATCH} number. Such releases are unstable and may not provide the intended compatibility with the specification in draft status.
Despite backward compatibility in {MINOR} or {PATCH} releases, API consumers are best to evaluate and determine their implementation does not disrupt use-case requirements.
The RESTful API provides both testing and live environments accessible over the Internet via HTTPS.
Consumers are to ensure firewall clearance on their edge network nodes for connecting to the APIs.
The convention used by API endpoints' URL are in the following format:
https://{ENV_DOMAIN_NAME}/{CONTEXT}/{VERSION}/{RESOURCE}{ENV_DOMAIN_NAME} indicates EDH's API domain names - respectively:
sandbox.api.edh.gov.sg, ortest.api.edh.gov.sg, orapi.edh.gov.sg, following/{CONTEXT}, indicates the context of the API call = /gov
/{VERSION} indicates the endpoint's release {MAJOR} version number
path - for this release = /v1
/{RESOURCE} indicates the API resource path name.
Any additional query string parameters are appended as needed.
The sandbox environment is used for your testing when developing your prototype. The Person Search API will return test data previously shared by our officer via email. For test data matters, please contact us.
sandbox.api.edh.gov.sgThe test enviroment is used for testing your application with the full security measures required in production. The Licence Search API will return test data previously shared by our officer via email. For test data matters, please contact us.
test.api.edh.gov.sgThe production enviroment is the actual live environment with full security measures and live data.
api.edh.gov.sgThe following are the scheduled downtimes for the various environments:
EDH's API gateway supports accessing of APIs via the following interfaces:
HTTP version 1.1 connection over TLS (Transport Layer Security) version 1.1 or 1.2 standards, and ciphersuites:
Below is the list of recommended cipher suites that you may use:
IMPORTANT: ensure your server supports TLS 1.1 or 1.2 and supports a cipher suite in the list above.
Accessing the RESTful API using prior versions of TLS or unsupported ciphersuites will result in connectivity errors. EDH's API gateway does not support 2-way TLS client nor mutual authentication.
API HTTP interface features:
Content-Type header application/json, alsoContent-Length header is omitted by having Transfer-Encoding header chunked emitted for streaming data, andAccept-Encoding: gzip and indicated in Content-Encoding header gzip.Access to all server-to-server APIs will be authenticated by EDH's API gateway. Prior to consumption of API, respective consumers are required to have:
Authentication methods provided by EDH's API gateway on internet:
NOTE: Test and Production Environments only
All server-to-server API requests are to be digitally signed, by including the following parameters and values in the Authorization header:
PKI_SIGN app_id="{app_id}",
nonce="{random_nonce}",
signature_method="RS256",
signature="{base64_url_percent_encoded_signature}",
timestamp="{unix_epoch_in_milliseconds}"Note: Above sample is separated by lines for ease-of-reading, and new-line denotations are to be omitted in the actual request.
{app_id} is the APP ID credential supplied upon onboarding,
{random_nonce} is an unique randomly generated text used for replay prevention,
{signature_algorithm} is the signature algorithm of the authenticating gateway.
RS256{base64_url_percent_encoded_signature} is the binary of the generated signature encoded in Base64 URL-safe format,
{unix_epoch_in_milliseconds} is the UNIX epoch time in milliseconds
Below is an example of an Authorization header for the sample application. Make sure you list the parameters in the sequence shown below.
Authorization: PKI_SIGN
app_id="STG2-EDH-SELF-TEST",
nonce="150590021034800",
signature_method="RS256",
signature="EEm+HEcNQajb5FkVd82zjojk+daYZXxSGPCOR2GHZeoyjZY1PK+aFMzHfWu7eJZYMa5WaEwWxdOdq5hjNbl8kHD7bMaOks7FgEPdjE++TNomfv7SMktDnIvZmPYAxhjb/C9POU2KT6tSlZT/Si/qMgD1cryaPwSeMoM59UZa1GzYmqlkveba7rma58uGwb3wZFH0n57UnouR6LYXDOOLkqi8uMZBuvRUvSJRXETAj2N0hT+4QJiN96Ct6IEQh/woZh0o74K5Ol9PpDSM08qC7Lj6N/k694J+hbBQVVviGn7/6mDkfbwdMDuoKs4t7NpqmAnwT+xaQSIZcexfrAVQYA==",
timestamp="1505900210349"NodeJS // generates the security headers for calling API gateway
function generateAuthorizationHeader(url, params, method, strContentType, authType, appId, keyCertContent, passphrase) {
if (authType == "L2") {
return generateRS256Header(url, params, method, strContentType, appId, keyCertContent, passphrase);
} else {
return "";
}
};
// Signing Your Requests
function generateRS256Header(url, params, method, strContentType, appId, keyCertContent, keyCertPassphrase) {
var nonceValue = nonce();
var timestamp = (new Date).getTime();
// A) Construct the Authorisation Token Parameters
var defaultAuthHeaders = {
"app_id": appId, // App ID assigned to your application
"nonce": nonceValue, // secure random number
"signature_method": "RS256",
"timestamp": timestamp // Unix epoch time
};
// B) Forming the Base String
// Base String is a representation of the entire request (ensures message integrity)
// i) Normalize request parameters
var baseParams = sortJSON(_.merge(defaultAuthHeaders, params));
var baseParamsStr = qs.stringify(baseParams);
baseParamsStr = qs.unescape(baseParamsStr); // url safe
// ii) concatenate request elements (HTTP method + url + base string parameters)
var baseString = method.toUpperCase() + "&" + url + "&" + baseParamsStr;
// C) Signing Base String to get Digital Signature
var signWith = {
key: fs.readFileSync(keyCertContent, 'utf8')
}; // Provides private key
// Load pem file containing the x509 cert & private key & sign the base string with it to produce the Digital Signature
var signature = crypto.createSign('RSA-SHA256')
.update(baseString)
.sign(signWith, 'base64');
// D) Assembling the Authorization Header
var strAuthHeader = "PKI_SIGN app_id=\"" + appId + // Defaults to 1st part of incoming request hostname
"\",nonce=\"" + nonceValue +
"\",signature_method=\"RS256\"" +
",signature=\"" + signature +
"\",timestamp=\"" + timestamp +
"\"";
return strAuthHeader;
};
NOTE: Licence Search APIs in Test and Production environments only
The response payload for the Licence Search API (for test and production environments) is first signed, then encrypted:
Encryption protects the data at rest while a signed payload means, if necessary, you will be able to pass this signed payload to a 3rd party where they can verify the payload's integrity with our public certificate.
In order to read the payload, you have to perform the following steps in order:
After doing the above steps, your application will be able to extract the payload in JSON format.
NodeJS // Sample Code for decrypting JWE
// Decrypt JWE using private key
function decryptJWE(header, encryptedKey, iv, cipherText, tag, privateKey) {
return new Promise((resolve, reject) => {
var keystore = jose.JWK.createKeyStore();
var data = {
"type": "compact",
"ciphertext": cipherText,
"protected": header,
"encrypted_key": encryptedKey,
"tag": tag,
"iv": iv,
"header": JSON.parse(jose.util.base64url.decode(header).toString())
};
keystore.add(fs.readFileSync(privateKey, 'utf8'), "pem")
.then(function(jweKey) {
// {result} is a jose.JWK.Key
jose.JWE.createDecrypt(jweKey)
.decrypt(data)
.then(function(result) {
resolve(JSON.parse(result.payload.toString()));
})
.catch(function(error) {
reject(error);
});
});
})
.catch (error => {
throw "Error with decrypting JWE";
})
}
The decrypted payload is signed according to JWS (JSON Web Signature) format, similar to the access token.
RS256.NodeJS // Sample Code for Verifying & Decoding JWS or JWT
function verifyJWS(jws, publicCert) {
// verify payload
// ignore notbefore check because it gives errors sometimes if the call is too fast.
try {
var decoded = jwt.verify(jws, fs.readFileSync(publicCert, 'utf8'), {
algorithms: ['RS256'],
ignoreNotBefore: true
});
return decoded;
}
catch(error) {
throw("Error with verifying and decoding JWS");
}
}
EDH Licence Search data follows a specific structure that you need to understand to traverse the data effectively. This section will explain the structure in detail.
The diagram below illustrates how the data is represented logically:

Data Items are attributes that can be requested in the API. Each top-level data item can either be a data item object or an array of data item objects. Each data item object will consist of the following properties:
source (see below)unavailable (in certain situations - see below) The source property indicates the source of data. Possible values are:
In each data item, there can be multiple data properties or arrays of data properties.
Each data property will contain either:
value property, orcode and desc properties, orNote:
value property can be strings, numbers, or dates.code and desc pairs will contain the code and its matching description.value is mutually exclusive from (code + desc); i.e. if there is a value, there will not be any code or desc.code, there will always be a desc - no value will be present.Exceptions: For these cases, the values will be directly in the property and not in a value, code or desc subproperty:
source and unavailabletype in address formatsSometimes, a requested data item or data property is not applicable to the person/entity/licence. For a full list, refer to the descriptions in each of the data properties of the Licence search data model.
When a requested data item is not applicable to the person/entity/licence:
source property will be 3In such cases, please ignore the data item completely.
The RESTful API(s) uses HTTP specification standard status codes to indicate the success or failure of each request. Except gateway errors, the response content will be in the following JSON format:
{
"code": "integer (int32)",
"message": "string"
}Refer to the individual API definitions for the error codes you might encounter for each API.
Please refer to the links below for the following supporting materials where relevant:
For technical queries, contact support@edh.gov.sg.
This API returns licence data from EDH.
Note: Null value indicates that an attribute is unavailable.
| licenceid required | string <= 10 characters Example: "30" Required URL path parameter of the licence ID. |
| client_id required | string Example: "STG-180099999K-TEST01" Unique ID for your application. |
| Authorization required | string Add authorization token constructed containing the RSA digital signature of the base string. Refer to Security > Request Signing on how this token should be generated. Note: Not required when calling Sandbox API. |
OK.
Note:
Unauthorized. This could be due to the scenarios below:
Details will be given in the error object returned.
Forbidden. Digital service is not registered with EDH.
MESSAGE: 'Digital Service is invalid'
Not Found. Licence does not exist in EDH.
MESSAGE: Licence is invalid.'
Error with request parameters or headers. Error details will be provided in the response body
// function to prepare request for Licence Search API function createLicenceSearchRequest(uinfin) { var url = _licenceSearchApiUrl + "/" + uinfin + "/"; var cacheCtl = "no-cache"; var method = "GET"; var request = null; // assemble params for Licence Search API var strParams = "client_id=" + _clientId + "&attributes=" + _attributes; var params = querystring.parse(strParams); // assemble headers for Licence Search API var strHeaders = "Cache-Control=" + cacheCtl; var headers = querystring.parse(strHeaders); var authHeaders; // Sign request and add Authorization Headers authHeaders = generateAuthorizationHeader( url, params, method, "", // no content type needed for GET _authLevel, _clientId, _privateKeyContent ); if (!_.isEmpty(authHeaders)) { _.set(headers, "Authorization", authHeaders); } // invoke token API var request = restClient.get(url); // Set headers if (!_.isUndefined(headers) && !_.isEmpty(headers)) request.set(headers); // Set Params if (!_.isUndefined(params) && !_.isEmpty(params)) request.query(params); return request; }