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 |
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 | |
countryOfResidence |
String | No | |
idType |
String | Yes | e.g. PASSPORT |
idNumber |
String | Yes | |
motherTongue |
String | No | |
bloodGroup |
String | No | |
gender |
Enum | Yes | e.g. MALE |
phone |
String | Yes | Include country code |
height |
Decimal | No | |
weight |
Decimal | No | |
englishService |
Boolean | No | |
emergencyContactName |
String | Yes | |
emergencyContactPhone |
String | Yes | |
emergencyContactEmail |
String | Yes | |
emergencyContactRelation |
String | Yes | e.g. Spouse |
medicalHistory |
String | No | |
allergy |
String | No | |
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": "US",
"idType": "PASSPORT",
"idNumber": "A12345678",
"gender": "MALE",
"phone": "+9779812345678",
"emergencyContactName": "Jane Doe",
"emergencyContactPhone": "+9779812345679",
"emergencyContactEmail": "jane@example.com",
"emergencyContactRelation": "Spouse",
"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://api.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 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 |
quoteId |
UUID | No | The quote backing this order. Strongly recommended |
excludedIncludedDeviceIds |
UUID[] | No | Bundled device IDs to drop |
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 |
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 |
extraFields |
JSON | No | Arbitrary metadata stored on the order snapshot |
dispatchInfo |
Object | No | Device dispatch planning information |
settlementType |
String | No | ONLINE 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.
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.
Do not send your own company
If your credential is a travel agency credential, your company is injected into travelCompanyIds automatically. Sending it yourself consumes one of your two agency slots.
If your credential is a sales company credential, 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.
Errors¶
| Status | Cause |
|---|---|
400 |
Validation failure, both or neither of routeId/customRouteName, more than two agencies, or a business rule failure |
401 |
Missing or invalid token |
404 |
touristId, ctgId, or routeId not found |
409 |
Conflicting resource or state |
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://api.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¶
- Payments — take payment for the order
- Promo Codes — apply discounts before payment
- Examples — the full flow end to end