VHMExpress Gateway API v1.0.1
Use the Gateway API to register products, create packages, list and track deliveries, and calculate delivery fees. All endpoints require merchant authentication via HTTP headers.
Base URL:
https://<your-domain>/api/v1/gatewayContent-Type:
application/json for POST request/response bodies.All gateway endpoints require merchant authentication. Requests and responses are logged for support and auditing. Base URL, API keys, and merchant IDs are provided upon request.
1. Authentication
All gateway endpoints require merchant authentication via HTTP headers.
Required headers
| Header | Required | Description |
|---|---|---|
HVMX-MERCHANT-ID | Yes | Your merchant identifier. |
HVMX-API-KEY | Yes | Your API key. |
Content-Type | Yes (for POST) | application/json for request bodies. |
401 Unauthorized
When authentication fails, the response body includes a machine-readable error code:
{
"status": 0,
"error": "<code>",
"message": "Unauthorized"
}
error | Meaning |
|---|---|
missing_merchant_id | HVMX-MERCHANT-ID header is missing or empty. |
missing_api_key | HVMX-API-KEY header is missing or empty. |
invalid_credentials | Merchant ID and/or API key are invalid. |
environment_not_allowed | Sandbox environment is not allowed (e.g. on production gateway). |
environment_mismatch | HVMX-ENVIRONMENT does not match the key's environment. |
2. Common response format
- Success:
status: 1, optionalmessage, and usually adataobject or array. - Error:
status: 0and amessage(and sometimeserror) describing the issue. - 404 / not found: Same JSON shape with
status: 0and an appropriatemessage.
3. Data API
Endpoints for districts and cities (e.g. address dropdowns, filters). Base path: /api/v1/gateway/data.
Get districts list
GET /api/v1/gateway/data/get-districts
Returns all active districts (for dropdowns, filters, etc.).
Request
Method: GET. Headers: Authentication headers only. No body.
Success response (200)
{
"status": 1,
"message": "OK",
"data": [
{ "id": 1, "name": "Colombo" },
{ "id": 2, "name": "Gampaha" }
]
}
data: array of { id: number, name: string }, sorted by name.
Get cities by district
GET /api/v1/gateway/data/get-cities/:district_id
Returns active cities for a given district (e.g. for address forms).
Request
Method: GET. URL parameter: district_id (integer) — district ID from the districts list. Headers: Authentication only. No body.
Example: GET /api/v1/gateway/data/get-cities/1
Success response (200)
{
"status": 1,
"message": "OK",
"data": [
{ "id": 10, "name": "Colombo 1", "district_id": 1 },
{ "id": 11, "name": "Colombo 2", "district_id": 1 }
]
}
data: array of { id: number, name: string, district_id: number }, sorted by name.
Error: Missing or invalid district_id: status: 0, message: "district_id is required" (or similar).
Get city by district and name
POST /api/v1/gateway/data/get-city-by-district-and-name
Returns a single city when it exists in the Cities table for the given district and city name. Matching is case-insensitive (e.g. colombo, COLOMBO, and Colombo all match the same city). Only active cities (flag = 1) are considered. Use this endpoint to resolve a city id from a district and city name (e.g. for delivery address or package creation).
Request
Method: POST. Headers: Authentication headers and Content-Type: application/json. Body: JSON object with the parameters below.
Body parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
district_id | number/string | Yes | District ID (e.g. from Get Districts or Get Cities by District). |
city_name | string | Yes | City name. Matched case-insensitively. |
Example request body:
POST /api/v1/gateway/data/get-city-by-district-and-name
Content-Type: application/json
{
"district_id": 5,
"city_name": "Colombo"
}
Success response (200)
data: object with the matched city.
| Field | Type | Description |
|---|---|---|
id | number | City ID. |
name | string | City name (as stored in DB). |
district_id | number | District ID. |
{
"status": 1,
"message": "OK",
"data": {
"id": 42,
"name": "Colombo",
"district_id": 5
}
}
Validation error (200, status 0)
When district_id or city_name is missing or empty:
{
"status": 0,
"message": "district_id is required"
}
Or message: "city_name is required".
Not found (404)
When no active city exists for the given district and name (case-insensitive):
{
"status": 0,
"message": "City not found for the given district and name"
}
Unauthorized (401)
When authentication fails (missing or invalid headers). See Authentication for error codes and response format.
Note: For a list of all cities in a district, use GET /api/v1/gateway/data/get-cities/:district_id instead.
4. Products API
Base path: /api/v1/gateway/products
4.1 Create product
POST /api/v1/gateway/products/create
Creates a new product for the authenticated vendor. Product is created in pending state until approved by admin.
Request
Headers: HVMX-MERCHANT-ID, HVMX-API-KEY, Content-Type: application/json
Body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Product name. Must be unique per vendor (among pending/approved). |
weight | number | Yes | Weight in kg. Can be 0. |
price | number | Yes | Price (e.g. LKR). ≥ 0. |
description | string | No | Product description. |
product_type_id | number | Yes | ID from product types. Must exist and be active. |
dimension_width | number | No | Width. Can be 0. |
dimension_height | number | No | Height. Can be 0. |
dimension_length | number | No | Length. Can be 0. |
Example request body:
{
"name": "Sample Product",
"weight": 1.5,
"price": 500,
"description": "",
"product_type_id": 1,
"dimension_width": 0,
"dimension_height": 0,
"dimension_length": 0
}
Response
Success (201 Created):
| Field | Type | Description |
|---|---|---|
status | number | 1 |
message | string | e.g. "Product created successfully (pending approval)" |
data | object | Created product (see below) |
data object: id, name, weight, price, description, product_type_id, dimension_width, dimension_height, dimension_length, flag (2 = pending), created_at (milliseconds since epoch, as string).
{
"status": 1,
"message": "Product created successfully (pending approval)",
"data": {
"id": 123,
"name": "Sample Product",
"weight": 1.5,
"price": 500,
"description": "",
"product_type_id": 1,
"dimension_width": 0,
"dimension_height": 0,
"dimension_length": 0,
"flag": 2,
"created_at": "1234567890123"
}
}
Errors: Validation (wrong types, missing required): status: 0, message describes the issue. Duplicate name (pending/approved): status: 0, message indicates duplicate product name. Invalid product_type_id: status: 0, message indicates invalid or inactive type.
4.2 Get product types
GET /api/v1/gateway/products/types
Returns the list of active product types (for dropdowns and when creating products).
Headers: HVMX-MERCHANT-ID, HVMX-API-KEY. Body: None.
Success (200): status: 1, data = array of { "id": number, "name": string }.
{
"status": 1,
"message": "OK",
"data": [
{ "id": 1, "name": "Electronics" },
{ "id": 2, "name": "Clothing" }
]
}
5. Packages API
Base path: /api/v1/gateway/packages
Packages are delivery consignments created via the gateway. Status in responses is normalized to: pending | completed | cancelled | returned | moving.
5.1 Create package
POST /api/v1/gateway/packages/create
Creates a delivery package for the authenticated vendor. Requires a valid receiver city from the city list; delivery fee is calculated by weight (slab-based). If the city is invalid, creation is rejected with "invalid city".
Request
Headers: Authentication + Content-Type: application/json
Body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
receiver_city_id | number | Yes | City ID from the data API (get-cities). Must be active. |
receiver_name | string | Yes | Receiver full name. |
receiver_phone | string | Yes | Receiver phone number. |
receiver_address | string | Yes | Delivery address line (street, building, etc.). |
weight | number | Yes | Package weight in kg. Must be > 0. |
price | number | Yes | Package value/price. ≥ 0. |
collection_type | string | Yes | One of: delivery_fee_only, delivery_fee_with_package_price, delivery_only. |
custom_package_id | string | Yes | Your unique reference for this package (unique per vendor). |
products | array | No | Optional list of { product_id: number, quantity: number }. Only approved products. |
package_price_includes_delivery_fee | boolean | No | When true, the vendor indicates that the given price already includes delivery (or delivery should not be charged). The amount to collect from receiving person (total_price) is adjusted per collection_type (see behaviour table below). Accepted as true/false, 1/0, or "true"/"false". Omit or false for default behaviour. |
Validation: If package_price_includes_delivery_fee is present, the value must be a boolean or one of: 0, 1, "true", "false". Any other type returns: status: 0, message: "package_price_includes_delivery_fee must be a boolean (true/false)".
Behaviour: total_price and collection
The amount to collect from receiving person (total_price) is derived from price, calculated delivery_fee, collection_type, and package_price_includes_delivery_fee:
collection_type | package_price_includes_delivery_fee | total_price (amount to collect from receiving person) |
|---|---|---|
delivery_fee_only | false or omitted | Delivery fee only |
delivery_fee_only | true | 0 (nothing to collect) |
delivery_fee_with_package_price | false or omitted | price + delivery fee |
delivery_fee_with_package_price | true | price (delivery already included; no extra charge) |
delivery_only | any | 0 (ignored; always 0) |
total_price is always ≥ 0. For delivery_only, package_price_includes_delivery_fee has no effect.
collection_type
Who pays what at delivery:
| Value | Meaning |
|---|---|
delivery_fee_only | Customer pays delivery fee only; package price is already paid. |
delivery_fee_with_package_price | Customer pays delivery fee + package price. |
delivery_only | No charge at delivery (both fee and package price already paid). |
Example request body:
{
"receiver_city_id": 10,
"receiver_name": "John Doe",
"receiver_phone": "0771234567",
"receiver_address": "123 Main St",
"weight": 2.5,
"price": 1000,
"collection_type": "delivery_fee_with_package_price",
"custom_package_id": "ORDER-001",
"products": [
{ "product_id": 1, "quantity": 1 }
]
}
Example: price already includes delivery (no extra delivery charge) — send package_price_includes_delivery_fee: true; then total_price will equal price for delivery_fee_with_package_price, or 0 for delivery_fee_only.
{
"weight": 2,
"price": 2000,
"receiver_city_id": 1,
"receiver_name": "Jane Doe",
"receiver_phone": "0779876543",
"receiver_address": "456 Park Ave, Colombo",
"collection_type": "delivery_fee_with_package_price",
"custom_package_id": "ORD-2025-002",
"package_price_includes_delivery_fee": true
}
Success response (201)
data follows the same package object shape as List and Details (see Package object). package_status is one of: pending, completed, cancelled, returned, moving.
Errors: receiver_city_id missing/invalid or city not in list: status: 0, message: "invalid city". custom_package_id already used for this vendor: status: 0, message indicates duplicate. No delivery route found for address: status: 0, message about serviced area. Invalid package_price_includes_delivery_fee (non-boolean): status: 0, message: "package_price_includes_delivery_fee must be a boolean (true/false)". Validation (e.g. invalid collection_type, product not found): status: 0, message describes the issue.
5.2 List packages
GET /api/v1/gateway/packages/list/:page
Returns a paginated list of packages created via the gateway for the authenticated vendor. Each page has a fixed size (e.g. 100).
Request
Headers: HVMX-MERCHANT-ID, HVMX-API-KEY
URL parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
page | number | Yes | Page number (1-based). |
Example: GET /api/v1/gateway/packages/list/1
Success (200): data.packages = array of Package objects; data.pagination = { page, per_page, total, total_pages }.
{
"status": 1,
"message": "OK",
"data": {
"packages": [
{
"id": 456,
"package_id": "D1C2ABC123XYZ",
"custom_package_id": "ORDER-001",
"package_status": "pending",
"weight": 2.5,
"price": 1000,
"delivery_fee": 250,
"total_price": 1250,
"delivery_city_id": 10,
"collection_type": "delivery_fee_with_package_price",
"receiver_name": "John Doe",
"receiver_phone": "0771234567",
"receiver_address": "123 Main St",
"delivery_date": "",
"created_at": "2025-03-05T10:00:00.000Z",
"products": [
{ "product_id": 1, "product_name": "Widget", "total_price": 500 }
]
}
],
"pagination": {
"page": 1,
"per_page": 100,
"total": 50,
"total_pages": 1
}
}
}
5.3 Get package details
POST /api/v1/gateway/packages/details
Returns a single package by package_id, only if it belongs to the authenticated vendor.
Request
Headers: HVMX-MERCHANT-ID, HVMX-API-KEY, Content-Type: application/json
Body:
| Field | Type | Required | Description |
|---|---|---|---|
package_id | string | Yes | The package_id (e.g. D1C2ABC123XYZ). |
Example: { "package_id": "D1C2ABC123XYZ" }
Success (200): status: 1, message, data = Package object.
Error: Package not found or not owned by vendor: 404, status: 0, message: "Package not found".
5.4 Calculate delivery fee
POST /api/v1/gateway/packages/delivery-fee
Returns the delivery fee for a given city (by ID) and weight. Uses the same slab-based calculation as create package. If the city is invalid, returns "invalid city" instead of a fee.
Request
Headers: Authentication + Content-Type: application/json
Body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
city_id | number | Yes | City ID from the data API (get-cities). Must be active. |
weight | number | Yes | Weight in kg. Must be > 0. |
Example: { "city_id": 10, "weight": 2.5 }
Success response (200)
{
"status": 1,
"message": "City recognized successfully.",
"data": {
"delivery_fee": 250,
"recognized_city": "Colombo 1",
"city_status": "City recognized successfully.",
"weight": 2.5
}
}
Errors: Missing/invalid city_id or city not in list: status: 0, message: "invalid city". Missing or invalid weight: status: 0, message describes the issue (e.g. weight required and must be > 0).
Note: The fee is calculated by weight only (slab-based: first kg, 2nd–5th kg, 6+ kg). City is used only for validation; the same weight in any valid city yields the same fee.
Package object
Used in Create Package response, List Packages (data.packages[]), and Get Package Details (data).
| Field | Type | Description |
|---|---|---|
id | number | Internal package ID. |
package_id | string | Unique package identifier (e.g. D1C2…). |
custom_package_id | string | Your reference (unique per vendor). |
package_status | string | pending | completed | cancelled | returned | moving. |
weight | number | Weight (kg). |
price | number | Package value. |
delivery_fee | number | Delivery fee (LKR). |
total_price | number | Total (depends on collection_type). |
delivery_city_id | number | null | Delivery city ID from city list. |
collection_type | string | delivery_fee_only | delivery_fee_with_package_price | delivery_only. |
receiver_name | string | Recipient name. |
receiver_phone | string | Recipient phone. |
receiver_address | string | Delivery address line. |
delivery_date | string | Filled when delivered/scheduled. |
created_at | string | UTC ISO 8601. |
products | array | { product_id, product_name, total_price }[]. |
products array element:
| Field | Type | Description |
|---|---|---|
product_id | number | Product ID. |
product_name | string | Product name. |
total_price | number | Line total (unit price × quantity). |
Delivery fee calculation (reference)
Delivery fee is weight-based only (no percentage, no city-based pricing):
- Weight is rounded up to the next whole kg (e.g. 2.3 kg → 3 kg).
- Three slab types from system pricing: First kg (0.01–1 kg): single price; Second slab (2–5 kg): per-kg price for 2nd–5th kg; Rest (6+ kg): per-kg price for 6th kg onwards.
- Formula: Rounded weight ≤ 1 kg → fee = first-kg price. 2–5 kg → fee = first-kg + (rounded − 1) × second-slab price. > 5 kg → fee = first-kg + 4 × second-slab + (rounded − 5) × rest-slab price.
This logic is used for both create package and delivery-fee API.
Endpoint summary
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/gateway/data/get-districts | Get districts list. |
| GET | /api/v1/gateway/data/get-cities/:district_id | Get cities by district. |
| POST | /api/v1/gateway/data/get-city-by-district-and-name | Get city by district and name (case-insensitive). |
| POST | /api/v1/gateway/products/create | Create product (pending approval). |
| GET | /api/v1/gateway/products/types | Get product types. |
| POST | /api/v1/gateway/packages/create | Create package. |
| GET | /api/v1/gateway/packages/list/:page | List packages (paginated). |
| POST | /api/v1/gateway/packages/details | Get package details by package_id. |
| POST | /api/v1/gateway/packages/delivery-fee | Calculate delivery fee by city_id and weight. |
All requests require headers HVMX-MERCHANT-ID and HVMX-API-KEY. POST bodies must be application/json. Error responses use status: 0 and include message.