Booking¶
Creating a booking is three steps: upload the traveller's documents, register the traveller, then create the order.
flowchart LR
A["POST /documents<br/>× 3 required"] --> B["POST /travellers<br/>→ touristId"]
B --> C["POST /orders<br/>→ orderRef"]
C --> D["POST /orders/{ref}/pay"]
style D stroke-dasharray: 5 5
Documents must exist before the traveller, and the traveller must exist before the order.
POST /documents¶
Uploads a single draft document and returns an ID you reference when creating the traveller.
Documents are uploaded as drafts — not yet attached to any traveller. They become attached when you pass their IDs to POST /travellers.
Authentication: required.
Request¶
POST /api/external/v1/documents
Authorization: Bearer <accessToken>
Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
file |
File | Yes | The document file |
documentType |
Enum | Yes | AVATAR, PASSPORT, SIGNATURE, or VISA |
| Document type | Accepted formats | Max size |
|---|---|---|
AVATAR, SIGNATURE |
JPG, PNG, HEIC | 25 MB |
PASSPORT, VISA |
JPG, PNG, HEIC, PDF | 10 MB |
The format is checked from the file content, not the file name or Content-Type.
Response¶
201 Created
{
"success": true,
"message": "Document uploaded",
"data": {
"documentId": "33333333-3333-3333-3333-333333333333",
"documentType": "PASSPORT",
"fileName": "passport.pdf",
"fileSize": 482910,
"contentType": "application/pdf",
"uploadedAt": "2026-07-07T03:30:00Z"
},
"timestamp": "2026-07-07T03:30:00Z"
}
Keep documentId — you need one per document type when registering the traveller.
Examples¶
import { openAsBlob } from "node:fs";
async function uploadDocument(path, documentType, token) {
const form = new FormData();
form.append("file", await openAsBlob(path), path.split("/").pop());
form.append("documentType", documentType);
const res = await fetch(`${BASE_URL}/documents`, {
method: "POST",
headers: { Authorization: `Bearer ${token}` }, // let fetch set Content-Type
body: form,
});
if (!res.ok) throw new Error(`Upload failed: ${res.status}`);
const { data } = await res.json();
return data.documentId;
}
def upload_document(path, document_type, access_token):
with open(path, "rb") as fh:
res = requests.post(
f"{BASE_URL}/documents",
headers={"Authorization": f"Bearer {access_token}"},
files={"file": fh},
data={"documentType": document_type},
timeout=30,
)
res.raise_for_status()
return res.json()["data"]["documentId"]
Do not set Content-Type manually on multipart requests
The multipart boundary is generated by your HTTP client. If you hard-code Content-Type: multipart/form-data without the boundary, the request body cannot be parsed and the upload fails.
Errors¶
Document upload failures are returned in a different shape from most errors on this API — a success: false envelope rather than the standard error object. See Errors.
POST /travellers¶
Registers between 1 and 50 travellers. The request body is a JSON array, even for a single traveller.
Authentication: required.
Request¶
| Field | Type | Required | Notes |
|---|---|---|---|
email |
String | Yes | Valid email address |
name |
String | Yes | Full name |
dateOfBirth |
Date | Yes | YYYY-MM-DD |
nationality |
String | Yes | Demonym, e.g. American. See Reference Data |
countryOfResidence |
String | No | See Reference Data |
idType |
String | Yes | e.g. PASSPORT. See Reference Data |
idNumber |
String | Yes | |
motherTongue |
String | No | See Reference Data |
bloodGroup |
String | No | See Reference Data |
gender |
Enum | Yes | Male, Female, or Other. See Reference Data |
phone |
String | Yes | <callingCode>-<digitsOnly>, e.g. +977-9812345678. See Reference Data |
height |
Decimal | No | |
weight |
Decimal | No | |
englishService |
Boolean | No | |
emergencyContactName |
String | Yes | |
emergencyContactPhone |
String | Yes | Same <callingCode>-<digitsOnly> shape as phone. See Reference Data |
emergencyContactEmail |
String | Yes | |
emergencyContactRelation |
String | Yes | e.g. Spouse. See Reference Data |
medicalHistory |
String | Yes | Declared condition categories, comma-joined. Send None if the traveller has none — see Reference Data |
allergy |
String | No | Free text |
avatarDocumentId |
UUID | Yes | From POST /documents with documentType=AVATAR |
passportDocumentId |
UUID | Yes | From POST /documents with documentType=PASSPORT |
signatureDocumentId |
UUID | Yes | From POST /documents with documentType=SIGNATURE |
visaDocumentId |
UUID | No | From POST /documents with documentType=VISA |
Three documents are mandatory
Every traveller needs an avatar, a passport, and a signature document already uploaded. Plan for three POST /documents calls per traveller before this request. Visa is optional.
[
{
"email": "john@example.com",
"name": "John Doe",
"dateOfBirth": "1990-04-15",
"nationality": "American",
"idType": "PASSPORT",
"idNumber": "A12345678",
"gender": "Male",
"phone": "+977-9812345678",
"emergencyContactName": "Jane Doe",
"emergencyContactPhone": "+977-9812345679",
"emergencyContactEmail": "jane@example.com",
"emergencyContactRelation": "Spouse",
"medicalHistory": "None",
"avatarDocumentId": "11111111-1111-1111-1111-111111111111",
"passportDocumentId": "22222222-2222-2222-2222-222222222222",
"signatureDocumentId": "33333333-3333-3333-3333-333333333333"
}
]
Response¶
201 Created — data is an array of traveller objects, in the same order as the request. Each carries the traveller's ID, which you pass to POST /orders as touristId.
GET /travellers¶
Lists the travellers your company has registered.
Authentication: required.
Query parameters¶
| Name | Type | Default | Description |
|---|---|---|---|
search |
String | — | Free-text search across traveller fields |
page |
Integer | 0 |
Zero-based page index |
size |
Integer | 20 |
Page size, capped at 100 |
sortBy |
String | createdAt |
Sort field |
sortDir |
String | desc |
asc or desc |
Response¶
200 OK — data is a paginated result whose content array holds traveller summaries.
curl -G https://apitest.himalayanguardian.com/api/external/v1/travellers \
-H "Authorization: Bearer $HGN_TOKEN" \
--data-urlencode "search=john" \
--data-urlencode "size=50"
Scope
You only ever see travellers registered under your own credential's company. Travellers registered by other partners are not visible and are not searchable.
POST /orders¶
Creates a single order, or a batch of orders in one group.
Authentication: required.
Request¶
| Field | Type | Required | Description |
|---|---|---|---|
touristId |
UUID | Yes | From POST /travellers |
ctgId |
UUID | Yes | Package. Use ctgId from the v2 quote |
routeId |
UUID | Conditional | Required when customRouteName is absent |
customRouteName |
String | Conditional | Required when routeId is absent. Mutually exclusive with routeId |
departureDate |
Date | Yes | Trip departure date, YYYY-MM-DD. Cannot be in the past (Nepal time) |
quoteId |
UUID | No | The quote backing this order. Strongly recommended |
excludedIncludedDeviceIds |
UUID[] | No | Bundled device IDs to drop. Each must be a device included in the selected package |
addedServices |
JSON | No | Optional additional services |
addedDevices |
JSON | No | Optional additional devices |
travelCompanyIds |
UUID[] | No | Registered agencies. See agency attribution |
customTravelAgencies |
Object[] | No | Display-only agencies. Each entry requires name |
orderGroupId |
UUID | No | Attach to an existing order group. Its route (or custom route name) and departure date must match this order |
referralCode |
String | No | Referral attribution code |
promoCode |
String | No | Promo code applied at creation. See Promo Codes |
directDiscountPercent |
Decimal | No | Discount drawn from your commission or incentive pool. Sales company credentials only; rejected for travel agencies |
extraFields |
JSON | No | Arbitrary metadata stored on the order snapshot |
dispatchInfo |
Object | No | Device dispatch planning information |
settlementType |
String | No | ONLINE (default) or CASH |
Single order¶
{
"touristId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"ctgId": "8d9f0e5a-1234-4f2e-bc3a-6b1a2c4d5e6f",
"routeId": "550e8400-e29b-41d4-a716-446655440000",
"departureDate": "2026-10-01",
"quoteId": "11111111-1111-1111-1111-111111111111"
}
Batch¶
Send an array to create several orders in one group. The response includes a group reference you can use for batch promo operations.
[
{
"touristId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"ctgId": "8d9f0e5a-1234-4f2e-bc3a-6b1a2c4d5e6f",
"routeId": "550e8400-e29b-41d4-a716-446655440000",
"departureDate": "2026-10-01"
},
{
"touristId": "ffffffff-bbbb-cccc-dddd-eeeeeeeeeeee",
"ctgId": "8d9f0e5a-1234-4f2e-bc3a-6b1a2c4d5e6f",
"routeId": "550e8400-e29b-41d4-a716-446655440000",
"departureDate": "2026-10-01"
}
]
One endpoint, two body shapes
POST /orders branches on the JSON shape of the body: an object creates one order, an array creates a group. A single-element array still creates a group — send a bare object if you want a standalone order.
Excluding bundled devices¶
Packages bundle devices, priced into the quote's totalAmount. To drop one, pass its deviceId — taken from the quote's includedDevices — in excludedIncludedDeviceIds.
{
"touristId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"ctgId": "8d9f0e5a-1234-4f2e-bc3a-6b1a2c4d5e6f",
"routeId": "550e8400-e29b-41d4-a716-446655440000",
"departureDate": "2026-10-01",
"quoteId": "11111111-1111-1111-1111-111111111111",
"excludedIncludedDeviceIds": ["a1b2c3d4-5678-4abc-9def-0123456789ab"]
}
Routes and custom routes¶
Supply exactly one of:
routeId— a catalogued route fromGET /reference/routes. Preferred.customRouteName— a free-text name, for trips that do not follow a catalogued route.
Sending both is rejected.
Before the order is accepted¶
The order is rejected if:
- the traveller is missing any of the three required documents (avatar, passport, signature)
- the traveller profile is missing name, date of birth, nationality, ID type, ID number, email, or emergency contact name/phone
- the traveller's age on the last covered day (
departureDate+ package days − 1) is outside the package's age range. Get a new quote with the traveller's date of birth and the real departure date - the route is a special route and the package is not approved for it
departureDateis in the past
A batch accepts at most 50 orders.
Agency attribution¶
Two fields record which agencies are involved:
| Field | Type | Grants order access | Earns commission |
|---|---|---|---|
travelCompanyIds |
UUID[] — registered HGN agencies | Yes | Yes |
customTravelAgencies |
Object[] — manual entries, name required |
No | No |
At most two agencies in total across both fields: two registered, two custom, or one of each.
What you can send depends on your credential's company type:
| Credential | travelCompanyIds |
customTravelAgencies |
|---|---|---|
| Travel agency | Ignored. Always set to your own company | Rejected (400) |
| Sales company | Used as sent | Allowed |
For sales company credentials, commission ownership resolves from your authenticated company context.
The singular fields travelCompanyId and customTravelAgency are not accepted. Order responses likewise expose only the plural travelCompanies and customTravelAgencies.
Response¶
201 Created
data is the created order for a single object, or a group payload for an array. Either way it carries the order reference — the identifier you use for payment and promo codes.
Promo codes fail softly here
A promoCode sent on this request is applied non-fatally. If the code is rejected, the order is still created and the reason is reported on promoApplyResult in the response.
Always check promoApplyResult rather than assuming a 201 means the discount applied. To recover, apply the code afterwards via POST /orders/{orderRef}/promo.
Policy issuance mode¶
You do not choose this. It is set when the order is created and returned as policyIssuanceMode:
| Departure | Mode | What happens |
|---|---|---|
| 48 hours away or less (Nepal time) | EXPRESS |
Policy is sent to the insurer right after payment |
| More than 48 hours away | NORMAL |
Policy is processed about 24 hours before coverage starts |
Errors¶
| Status | Cause |
|---|---|
400 |
Validation failure, both or neither of routeId/customRouteName, more than two agencies, departure in the past, traveller's age on the last covered day outside the package range, package not approved for a special route, or a business rule failure |
401 |
Missing or invalid token |
404 |
touristId, ctgId, or routeId not found |
409 |
Traveller missing required documents or profile fields, or orderGroupId route/departure mismatch |
GET /orders/{orderIdentifier}¶
Fetches full order details.
Authentication: required.
Path parameters¶
| Name | Type | Description |
|---|---|---|
orderIdentifier |
String | Order reference (ORD-2026-000058) or order UUID |
Response¶
200 OK — data is the full order detail object, including status, the traveller, the package, pricing, agency attribution (travelCompanies, customTravelAgencies), and any applied promo codes.
curl https://apitest.himalayanguardian.com/api/external/v1/orders/ORD-2026-000058 \
-H "Authorization: Bearer $HGN_TOKEN"
Ownership is enforced
You can only read orders placed by your own company. Requesting another partner's order returns 403, and an unknown identifier returns 404.
Next steps¶
- Reference Data — the fixed value sets behind
nationality,gender,bloodGroup,idType, the health self-declaration, and calling codes - Payments — take payment for the order
- Promo Codes — apply discounts before payment
- Examples — the full flow end to end