Skip to content

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

curl -X POST https://api.himalayanguardian.com/api/external/v1/documents \
  -H "Authorization: Bearer $HGN_TOKEN" \
  -F "file=@passport.pdf" \
  -F "documentType=PASSPORT"
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

POST /api/external/v1/travellers
Authorization: Bearer <accessToken>
Content-Type: application/json
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 Createddata 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 OKdata 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

POST /api/external/v1/orders
Authorization: Bearer <accessToken>
Content-Type: application/json
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 from GET /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 OKdata 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