Authentication¶
Every endpoint except POST /auth/token requires a bearer token. Tokens are short-lived and obtained by exchanging the clientId and clientSecret issued to you by HGN.
Credentials¶
HGN issues each partner a credential pair tied to a company account.
| Field | Description |
|---|---|
clientId |
Public identifier for your integration |
clientSecret |
Secret value. Treat it like a password |
Credential types¶
Your credential is bound to one of two company types, which changes how orders attribute commission:
| Type | Behaviour on POST /orders |
|---|---|
| Travel agency | Your company is injected into travelCompanyIds automatically. Do not send it yourself |
| Sales company | Commission ownership resolves from your authenticated company context |
Protect your client secret
The secret authenticates as your company and can create real, billable orders.
- Store it in a secrets manager or server-side environment variable — never in source control.
- Never ship it to a browser, mobile app, or any client you do not control.
- Rotate it immediately if exposed. Contact the HGN integrations team to rotate or deactivate a credential.
POST /auth/token¶
Exchanges partner credentials for a bearer token.
Authentication: none — this is the only unauthenticated endpoint.
Request¶
| Field | Type | Required | Validation |
|---|---|---|---|
clientId |
String | Yes | Non-blank |
clientSecret |
String | Yes | Non-blank |
Response¶
200 OK
{
"success": true,
"message": "Token issued",
"data": {
"accessToken": "8f2a9c1e4b7d3a6f0e5c8b2d9a4f7e1c3b6d0a8f",
"tokenType": "Bearer",
"expiresIn": 3600
},
"timestamp": "2026-07-07T03:30:00Z"
}
| Field | Type | Description |
|---|---|---|
accessToken |
String | The bearer token to send on every other request |
tokenType |
String | Always Bearer |
expiresIn |
Long | Token lifetime in seconds |
Examples¶
const res = await fetch(
"https://api.himalayanguardian.com/api/external/v1/auth/token",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
clientId: process.env.HGN_CLIENT_ID,
clientSecret: process.env.HGN_CLIENT_SECRET,
}),
}
);
if (!res.ok) throw new Error(`Auth failed: ${res.status}`);
const { data } = await res.json();
const accessToken = data.accessToken;
$ch = curl_init('https://api.himalayanguardian.com/api/external/v1/auth/token');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'clientId' => getenv('HGN_CLIENT_ID'),
'clientSecret' => getenv('HGN_CLIENT_SECRET'),
]),
]);
$body = json_decode(curl_exec($ch), true);
$accessToken = $body['data']['accessToken'];
Using the token¶
Send the token as a bearer credential on every other /api/external/v1/** request.
The token is opaque — do not parse it
accessToken is an unguessable random string validated server-side on every request. It is not a JWT. It has no readable claims, no embedded expiry, and no signature you can verify.
Do not decode it, do not inspect it, and do not derive an expiry from it. Track expiry using the expiresIn value returned alongside it.
Token lifecycle¶
Tokens expire after expiresIn seconds. There is no refresh token — when a token expires, call POST /auth/token again.
The recommended pattern is to cache the token in your backend and refresh it slightly early, so an in-flight request never lands on a just-expired token.
let cached = { token: null, expiresAt: 0 };
async function getAccessToken() {
// Refresh 60s before actual expiry to absorb clock skew and latency.
if (cached.token && Date.now() < cached.expiresAt - 60_000) {
return cached.token;
}
const res = await fetch(`${BASE_URL}/auth/token`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
clientId: process.env.HGN_CLIENT_ID,
clientSecret: process.env.HGN_CLIENT_SECRET,
}),
});
if (!res.ok) throw new Error(`Auth failed: ${res.status}`);
const { data } = await res.json();
cached = {
token: data.accessToken,
expiresAt: Date.now() + data.expiresIn * 1000,
};
return cached.token;
}
Handle 401 as a retry, once
Even with early refresh, a token can be revoked mid-flight. On a 401, discard your cached token, fetch a new one, and retry the request once. If the retry also returns 401, the credential itself is the problem — stop and alert, rather than looping.
Authentication failures¶
A request is rejected with 401 Unauthorized when:
- the
Authorizationheader is missing - the token is unknown, expired, or revoked
- the credential has been revoked or deleted
- the company that owns the credential is inactive or deleted
These rejections happen before your request reaches the endpoint, so they use a reduced envelope with no timestamp field:
Distinguishing causes
All four conditions return 401. The message differs but is not a stable contract — do not branch on it. If tokens suddenly stop working across all your integrations at once, suspect a revoked credential or a deactivated company account and contact HGN.
See Errors for the other error shapes this API can return.
Next steps¶
- Availability — price a trip and get a
quoteId - Examples — a complete booking from token to payment