Skip to content

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

POST /api/external/v1/auth/token
Content-Type: application/json
Field Type Required Validation
clientId String Yes Non-blank
clientSecret String Yes Non-blank
{
  "clientId": "hgn_ext_example",
  "clientSecret": "secret-value"
}

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

curl -X POST https://api.himalayanguardian.com/api/external/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "hgn_ext_example",
    "clientSecret": "secret-value"
  }'
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;
import os, requests

res = requests.post(
    "https://api.himalayanguardian.com/api/external/v1/auth/token",
    json={
        "clientId": os.environ["HGN_CLIENT_ID"],
        "clientSecret": os.environ["HGN_CLIENT_SECRET"],
    },
    timeout=10,
)
res.raise_for_status()

access_token = res.json()["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.

Authorization: Bearer 8f2a9c1e4b7d3a6f0e5c8b2d9a4f7e1c3b6d0a8f

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 Authorization header 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:

{
  "success": false,
  "message": "Missing Authorization header",
  "data": null
}

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