Availability and Quoting¶
Before creating an order you need a package (ctgId) and a price. The fastest way to get both is to request a quote — it resolves the correct package for the route, traveller age, and altitude, and returns the exact amount payable.
The catalogue endpoints are there for browsing and for building your own selection UI.
Quote v1 is deprecated — move to v2 by 31 December 2026
POST /api/external/v1/reference/quote (quote from ages) is deprecated and will stop working after 31 December 2026. Use POST /api/external/v2/reference/quote, which takes the traveller's date of birth instead of an age.
Until then v1 keeps working unchanged, and every v1 response carries these headers so you can detect it in code:
Deprecation: true
Sunset: Thu, 31 Dec 2026 23:59:59 GMT
Link: </api/external/v2/reference/quote>; rel="successor-version"
See Migrating from v1.
POST /v2/reference/quote¶
Prices a trip for one traveller from their date of birth and returns the recommended package.
Authentication: required. Use the same bearer token as every other endpoint.
This is the only v2 endpoint
The path is /api/external/v2/reference/quote. Every other endpoint stays under /api/external/v1.
One traveller per quote
A quote covers exactly one person. dobs must contain exactly one date. To price a group, request one quote per traveller and sum the results.
Request¶
POST /api/external/v2/reference/quote
Authorization: Bearer <accessToken>
Content-Type: application/json
| Field | Type | Required | Validation |
|---|---|---|---|
dobs |
Date[] | Yes | Exactly one date of birth, YYYY-MM-DD, in the past |
nationality |
String | Yes | Non-blank, max 100 characters |
startDate |
Date | Yes | First day of cover, YYYY-MM-DD |
durationDays |
Integer | Yes | Days of the trip, counting startDate as day 1. Positive |
routeId |
UUID | No | A known route. See GET /reference/routes |
maxAltitude |
Integer | Conditional | Required when routeId is omitted. 0–9000 metres |
Passing routeId is preferred: it resolves the altitude for you and returns a route name you can show the customer.
{
"nationality": "American",
"dobs": ["1998-03-14"],
"routeId": "550e8400-e29b-41d4-a716-446655440000",
"startDate": "2026-10-01",
"durationDays": 14
}
How the age is worked out¶
You send the date of birth; HGN works out the age. The traveller is aged on the last day of the policy, because that is the oldest they will be while insured and the package has to cover that age.
| Package | Last day of the policy |
|---|---|
| Special route | startDate + durationDays − 1 (the trip) |
| Any other package | startDate + package days − 1. A package can cover more days than the trip, for example a 10-day trek on a 14-day package is insured for 14 days |
Ages are whole years and never rounded up: 17 years and 11 months is 17. A traveller who has a birthday before the last day of the policy is quoted at the new age, and may get a different package.
The response tells you exactly what was used: endDate is the last insured day and agesUsed the age the price is for.
Response¶
200 OK
{
"success": true,
"message": "Quote generated successfully",
"data": {
"quoteId": "11111111-1111-1111-1111-111111111111",
"quoteStatus": "QUOTED",
"createdAt": "2026-07-07T03:30:00Z",
"ctgId": "8d9f0e5a-1234-4f2e-bc3a-6b1a2c4d5e6f",
"ctgCode": "CTG/TOURIST/MIDDLE",
"ctgName": "Tourist – Middle Altitude",
"nationality": "American",
"numberOfPersons": 1,
"ages": [28],
"agesUsed": [28],
"routeId": "550e8400-e29b-41d4-a716-446655440000",
"routeName": "Annapurna Base Camp",
"maxAltitude": 4130,
"startDate": "2026-10-01",
"endDate": "2026-10-14",
"durationDays": 14,
"totalAmount": 590.00,
"insuranceCompany": "Example Insurance",
"coverage": [
{ "code": "LIB_DEATH", "name": "Accidental Death and Disability", "coverageAmount": 20000.00 },
{ "code": "LIB_MEDICAL", "name": "Accidental Medical Treatment", "coverageAmount": 3500.00 },
{ "code": "LIB_TRANSPORT", "name": "Emergency Medical Transportation", "coverageAmount": 8000.00 },
{ "code": "LIB_REPATRIATE", "name": "Repatriation of Corpse", "coverageAmount": 2000.00 }
],
"includedDevices": [
{
"deviceId": "a1b2c3d4-5678-4abc-9def-0123456789ab",
"name": "Tracer M3",
"description": "CTG tracer device with emergency SOS for high-altitude routes",
"quantity": 1,
"priceForDuration": 40.00
}
],
"deviceSelectionPricing": {
"hasIncludedMandatoryDevice": true,
"devicePriceImpact": 51.00
}
},
"timestamp": "2026-07-07T03:30:00Z"
}
The fields are the same as v1 (see the field table below), with two differences:
| Field | v2 meaning |
|---|---|
endDate |
Last insured day, inclusive. In v1 it was startDate + duration, the day after |
agesUsed |
New. The age the quote is priced for, taken on endDate |
The order checks the same age
When you create the order, HGN ages the traveller on the order's last covered day and checks the package covers that age. If it does not, for example because you changed the departure date, the order is rejected with 400 and you need a new quote.
Dropping an included device¶
includedDevices are bundled into totalAmount. To exclude one, pass its deviceId in excludedIncludedDeviceIds when you create the order.
The order total without the device is totalAmount − deviceSelectionPricing.devicePriceImpact. Do not subtract priceForDuration: device fees also carry markup and VAT, so the real drop is larger.
Examples¶
curl -X POST https://apitest.himalayanguardian.com/api/external/v2/reference/quote \
-H "Authorization: Bearer $HGN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"nationality": "American",
"dobs": ["1998-03-14"],
"routeId": "550e8400-e29b-41d4-a716-446655440000",
"startDate": "2026-10-01",
"durationDays": 14
}'
const res = await fetch("https://apitest.himalayanguardian.com/api/external/v2/reference/quote", {
method: "POST",
headers: {
Authorization: `Bearer ${await getAccessToken()}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
nationality: "American",
dobs: ["1998-03-14"],
routeId: "550e8400-e29b-41d4-a716-446655440000",
startDate: "2026-10-01",
durationDays: 14,
}),
});
const { data: quote } = await res.json();
console.log(quote.ctgName, quote.totalAmount, quote.endDate, quote.agesUsed);
res = requests.post(
"https://apitest.himalayanguardian.com/api/external/v2/reference/quote",
headers={"Authorization": f"Bearer {access_token}"},
json={
"nationality": "American",
"dobs": ["1998-03-14"],
"routeId": "550e8400-e29b-41d4-a716-446655440000",
"startDate": "2026-10-01",
"durationDays": 14,
},
timeout=10,
)
res.raise_for_status()
quote = res.json()["data"]
print(quote["ctgName"], quote["totalAmount"], quote["endDate"], quote["agesUsed"])
Errors¶
| Status | Cause |
|---|---|
400 |
Validation failure — no date of birth or more than one, a date of birth not in the past, missing or non-positive durationDays, missing both routeId and maxAltitude, or no package covers the traveller for the whole policy |
401 |
Missing or invalid token |
404 |
routeId does not match a known route |
Migrating from v1¶
| v1 request | v2 request |
|---|---|
POST /api/external/v1/reference/quote |
POST /api/external/v2/reference/quote |
ages: [28] |
dobs: ["1998-03-14"] |
duration: 14 or endDate |
durationDays: 14 |
numberOfPersons |
removed |
Nothing else changes: same token, same quoteId/ctgId to echo into POST /orders, same catalogue and routes. In the response, read endDate as the last insured day and use agesUsed if you show the age.
POST /reference/quote (deprecated)¶
Deprecated — stops working after 31 December 2026
Use POST /v2/reference/quote. See Migrating from v1.
Prices a trip from an age and returns the recommended package.
Authentication: required.
One traveller per quote
A quote covers exactly one person. ages must contain exactly one value. To price a group, request one quote per traveller and sum the results.
Request¶
POST /api/external/v1/reference/quote
Authorization: Bearer <accessToken>
Content-Type: application/json
| Field | Type | Required | Validation |
|---|---|---|---|
nationality |
String | Yes | Non-blank, max 100 characters |
ages |
Integer[] | Yes | Exactly one age, each 1–120 |
startDate |
Date | Yes | YYYY-MM-DD |
endDate |
Date | Conditional | Required if duration is omitted. Must be after startDate |
duration |
Integer | Conditional | Required if endDate is omitted. Positive integer, in days. If you send both, duration must equal endDate − startDate |
routeId |
UUID | No | A known route. See GET /reference/routes |
maxAltitude |
Integer | Conditional | Required when routeId is omitted. 0–9000 metres |
numberOfPersons |
Integer | No | Deprecated. If sent, must be 1. Omit it |
Trip length: send endDate or duration (both is allowed only if they agree). For a special route you may omit both; the route's minimum duration is used.
Altitude: send routeId or maxAltitude. Passing routeId is preferred: it resolves the altitude for you and returns a route name you can show the customer.
{
"nationality": "American",
"ages": [28],
"routeId": "550e8400-e29b-41d4-a716-446655440000",
"startDate": "2026-10-01",
"duration": 14
}
Response¶
200 OK
{
"success": true,
"message": "Quote generated successfully",
"data": {
"quoteId": "11111111-1111-1111-1111-111111111111",
"quoteStatus": "QUOTED",
"createdAt": "2026-07-07T03:30:00Z",
"ctgId": "8d9f0e5a-1234-4f2e-bc3a-6b1a2c4d5e6f",
"ctgCode": "CTG/TOURIST/MIDDLE",
"ctgName": "Tourist – Middle Altitude",
"nationality": "American",
"numberOfPersons": 1,
"ages": [28],
"routeId": "550e8400-e29b-41d4-a716-446655440000",
"routeName": "Annapurna Base Camp",
"maxAltitude": 4130,
"startDate": "2026-10-01",
"endDate": "2026-10-15",
"durationDays": 14,
"totalAmount": 590.00,
"insuranceCompany": "Example Insurance",
"coverage": [
{ "code": "LIB_DEATH", "name": "Accidental Death and Disability", "coverageAmount": 20000.00 },
{ "code": "LIB_MEDICAL", "name": "Accidental Medical Treatment", "coverageAmount": 3500.00 },
{ "code": "LIB_TRANSPORT", "name": "Emergency Medical Transportation", "coverageAmount": 8000.00 },
{ "code": "LIB_REPATRIATE", "name": "Repatriation of Corpse", "coverageAmount": 2000.00 }
],
"includedDevices": [
{
"deviceId": "a1b2c3d4-5678-4abc-9def-0123456789ab",
"name": "Tracer M3",
"description": "CTG tracer device with emergency SOS for high-altitude routes",
"quantity": 1,
"priceForDuration": 40.00
}
],
"deviceSelectionPricing": {
"hasIncludedMandatoryDevice": true,
"devicePriceImpact": 51.00
}
},
"timestamp": "2026-07-07T03:30:00Z"
}
| Field | Type | Description |
|---|---|---|
quoteId |
UUID | Pass back as quoteId when creating the order |
quoteStatus |
String | QUOTED for a new quote |
createdAt |
Timestamp | When the quote was generated |
ctgId |
UUID | Recommended package. Pass back as ctgId when creating the order |
ctgCode |
String | Package display code |
ctgName |
String | Package display name |
nationality |
String | Echoed from the request |
numberOfPersons |
Integer | Always 1 |
ages |
Integer[] | Echoed from the request |
routeId |
UUID | Resolved route |
routeName |
String | Resolved route name |
maxAltitude |
Integer | Route maximum altitude in metres |
startDate |
Date | Echoed from the request |
endDate |
Date | startDate + durationDays. Sent value is echoed; if you sent only duration, it is calculated |
durationDays |
Integer | Trip length in days: endDate − startDate |
totalAmount |
Decimal | Total payable for this quote |
insuranceCompany |
String | Underwriter display name |
coverage |
Object[] | Liability coverages: code, name, coverageAmount |
includedDevices |
Object[] | Bundled devices: deviceId, name, description, quantity, priceForDuration |
deviceSelectionPricing |
Object | hasIncludedMandatoryDevice, devicePriceImpact: how much totalAmount drops if the bundled device is excluded |
The quote is a complete order blueprint
You do not need a catalogue lookup between quoting and ordering. Map the quote straight onto the order:
| Quote field | Order field |
|---|---|
ctgId |
ctgId |
quoteId |
quoteId |
routeId |
routeId |
startDate |
departureDate |
Examples¶
curl -X POST https://apitest.himalayanguardian.com/api/external/v1/reference/quote \
-H "Authorization: Bearer $HGN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"nationality": "American",
"ages": [28],
"routeId": "550e8400-e29b-41d4-a716-446655440000",
"startDate": "2026-10-01",
"duration": 14
}'
const res = await fetch(`${BASE_URL}/reference/quote`, {
method: "POST",
headers: {
Authorization: `Bearer ${await getAccessToken()}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
nationality: "American",
ages: [28],
routeId: "550e8400-e29b-41d4-a716-446655440000",
startDate: "2026-10-01",
duration: 14,
}),
});
const { data: quote } = await res.json();
console.log(quote.ctgName, quote.totalAmount);
res = requests.post(
f"{BASE_URL}/reference/quote",
headers={"Authorization": f"Bearer {access_token}"},
json={
"nationality": "American",
"ages": [28],
"routeId": "550e8400-e29b-41d4-a716-446655440000",
"startDate": "2026-10-01",
"duration": 14,
},
timeout=10,
)
res.raise_for_status()
quote = res.json()["data"]
print(quote["ctgName"], quote["totalAmount"])
Errors¶
| Status | Cause |
|---|---|
400 |
Validation failure — more than one age, age out of range, missing both endDate and duration, endDate not after startDate, duration not matching the dates, missing both routeId and maxAltitude, or numberOfPersons other than 1 |
401 |
Missing or invalid token |
404 |
routeId does not match a known route |
A validation failure returns field-level detail. See Errors.
GET /reference/ctgs¶
Lists the active public package catalogue. Use this to build a browse-and-select UI, or to look up package detail you want to display alongside a quote.
Authentication: required.
Query parameters¶
| Name | Type | Default | Description |
|---|---|---|---|
search |
String | — | Free-text search |
altitudeBand |
String | — | Filter by altitude band |
durationBand |
String | — | Filter by duration band |
ageBand |
String | — | Filter by age band |
productType |
String | — | Filter by insurance product type |
page |
Integer | 0 |
Zero-based page index |
size |
Integer | 20 |
Page size, capped at 100 |
sortBy |
String | displayOrder |
Sort field |
sortDir |
String | asc |
asc or desc |
Response¶
200 OK — data is a paginated result whose content array holds package objects.
| Field | Type | Description |
|---|---|---|
ctgId |
UUID | Package ID for quoting and ordering |
ctgCode |
String | Display code |
ctgName |
String | Display name |
insuranceProductId |
String | Underlying insurance product ID |
routeId |
UUID | Linked route, for special-route products |
insuranceCompany |
String | Underwriter display name |
minAltitude |
Integer | Altitude band lower bound, metres |
maxAltitude |
Integer | Altitude band upper bound, metres |
baseDurationDays |
Integer | Base duration |
minAge |
Integer | Minimum eligible age |
maxAge |
Integer | Maximum eligible age |
baseCtgPrice |
Decimal | Stored base/display price |
ctgPriceForDuration |
Decimal | Price calculated for the duration |
includedDevicesPrice |
Decimal | Mandatory included-device price |
totalPrice |
Decimal | Total public listing price |
liabilities |
Object[] | Liability coverages |
includedDevices |
Object[] | Included product devices |
description |
String | Product description |
termsConditions |
String | Terms summary or link |
curl -G https://apitest.himalayanguardian.com/api/external/v1/reference/ctgs \
-H "Authorization: Bearer $HGN_TOKEN" \
--data-urlencode "search=tourist" \
--data-urlencode "size=50"
Listed price vs. quoted price
totalPrice here is the public listing price for the package. The amount your customer actually pays is totalAmount from POST /v2/reference/quote, which accounts for the specific traveller, route, and dates. Always charge against the quote.
GET /reference/routes¶
Lists active trekking routes. Only active routes are returned — there is no parameter to include inactive ones.
Authentication: required.
Query parameters¶
| Name | Type | Default | Description |
|---|---|---|---|
search |
String | — | Free-text search |
region |
String | — | Filter by region |
trekName |
String | — | Filter by trek name |
routeType |
String | — | Filter by route type |
routeCategory |
String | — | Filter by route category |
page |
Integer | 0 |
Zero-based page index |
size |
Integer | 20 |
Page size, capped at 100 |
sortBy |
String | name |
Sort field |
sortDir |
String | asc |
asc or desc |
Response¶
200 OK — data is a paginated result whose content array holds route summary objects. These are the same route summaries the public booking widget uses.
The field you need for the rest of the flow is routeId, which you pass to POST /v2/reference/quote and then to POST /orders. Routes are also filterable by region, trek name, route type, and route category, as shown above.
curl -G https://apitest.himalayanguardian.com/api/external/v1/reference/routes \
-H "Authorization: Bearer $HGN_TOKEN" \
--data-urlencode "region=Annapurna" \
--data-urlencode "sortBy=name"
This endpoint uses a different pagination envelope
GET /reference/routes wraps its results in a different page envelope from GET /reference/ctgs and GET /travellers. All three expose a content array; the surrounding metadata field names differ. If you share one pagination helper across endpoints, read content and avoid depending on the metadata field names.
Custom routes¶
If a trip does not follow a catalogued route, you can skip routeId entirely and create the order with a customRouteName instead. See Booking. Quotes still need either a routeId or a maxAltitude.