Forwarding API
The API lets your system:
- Send requests for quotation (RFQ) for forwarding shipments (air, sea, road, customs...).
- Provide additional information when Bell asks for it.
- Receive quotations, then accept, ask to renegotiate, or reject them.
- Follow shipment progress and exchange documents.
- Receive webhooks when a request's status changes or progress is updated.
1. General information
| Base URL | https://expressbe.bellvn.com/api/partner/fwd |
| Format | JSON, UTF-8 (Content-Type: application/json), except document uploads, which use multipart/form-data |
| Authentication | Authorization: Bearer <token> issued by Bell. This is the same token as the courier Partner API, if you already have one |
| Rate limit | 120 requests per minute per customer by default. Above that you receive 429; see the Retry-After header |
| Dates | Dates as YYYY-MM-DD, timestamps as ISO-8601 (2026-09-23T10:15:00+07:00) |
Each request is identified by its rfq_code (a code issued by Bell, e.g. RFQ-HCM-2609-0012). You can attach your own reference with partner_ref. You can only see your own requests.
Code fields (status, operation_status, doc_type...) are stable and in English. Display fields such as message, status_label, operation_status_label and catalog labels are returned in Vietnamese. Build your integration on the code fields.
Response structure
Success:
{ "status": "success", "message": "…", "data": { … } }
Validation error (422): errors lists the errors per field.
{
"status": "error",
"message": "Chưa khai báo điểm nhận hàng.",
"errors": { "origin": ["Chưa khai báo điểm nhận hàng."] }
}
| HTTP | Meaning |
|---|---|
| 200 / 201 | Success (201 = a new request was created) |
| 401 | Missing, expired, invalid or revoked token. Contact Bell for a new token |
| 404 | The request does not exist or does not belong to you |
| 422 | Invalid data, or the action is not allowed in the current status |
| 429 | Rate limit exceeded |
| 5xx | Error on Bell's side; you can retry |
2. Request status
status | Meaning | What you should do |
|---|---|---|
submitted | Bell has received the request | Wait |
need_info | Bell needs more information; details are in the info_request field | Call PUT /orders/{rfq_code} |
quoting | Bell is preparing the quotation | Wait |
quoted | A quotation is available | Review the quotation, then accept / negotiate / reject |
confirmed | You have accepted; the order is being prepared | Send documents if needed |
in_transit | In transit; details in operation_status | Follow tracking |
completed | Shipment completed | — |
cancelled | Cancelled or unsuccessful | — |
Typical flow: submitted → quoting → quoted → confirmed → in_transit → completed. A request can go back to need_info or quoting, for example when you ask to renegotiate and Bell revises the price.
When status = in_transit, the operation_status field shows detailed progress: received, booked, picked_up, customs_declaring, export_cleared, departed, in_transit, arrived, import_cleared, delivering, delivered… The full list with labels is available from GET /meta.
3. Catalogs — GET /meta
Returns the codes used when sending a request. Call it periodically (e.g. once a day) and cache the result.
{
"data": {
"services": [ { "code": "AIR", "name": "Vận chuyển hàng không", "name_en": "Air Freight", "transport_mode": "air" } ],
"statuses": [ { "value": "submitted", "label": "Đã gửi yêu cầu" } ],
"operation_statuses": [ { "value": "picked_up", "label": "Đã lấy hàng", "order": 6 } ],
"delivery_scopes": { "door_door": "Door to Door" },
"incoterms": { "FOB": "FOB" },
"special_requirements": { "insurance": "Yêu cầu bảo hiểm" },
"container_types": { "40HC": "40HC" },
"document_types": { "commercial_invoice": "Commercial Invoice" },
"reject_reasons": [ { "value": "price_high", "label": "Giá cao" } ]
}
}
At the time of writing, the codes in use are:
| Catalog | Values |
|---|---|
incoterm | EXW, FCA, FOB, CFR, CIF, CPT, CIP, DAP, DPU, DDP, OTHER |
delivery_scope | door_cy, cy_cy, cy_door, door_door, door_cfs, cfs_cfs, cfs_door, airport_airport, door_airport, airport_door, warehouse_warehouse, other |
special_requirements | time, vehicle, carrier, packing, insurance, customs, permit, ior, cod, timed_delivery, confidential, other |
container_type | 20DC, 40DC, 40HC, 45HC, reefer, other |
4. Send a request for quotation — POST /orders
The request goes straight to Bell's pricing team; there is no draft step. If data is missing, the whole request is rejected (422) and nothing is created.
Duplicate protection: if you send a partner_ref, sending the same partner_ref again (for example, retrying after a timeout) does not create a new request; it returns the existing one with HTTP 200 instead of 201. We recommend always sending partner_ref.
Fields
| Field | Required | Description |
|---|---|---|
partner_ref | recommended | Your reference, up to 100 characters, unique within your account |
contact_name | ✔ | Contact person |
contact_phone, contact_email, contact_position | Default to your account details if empty | |
service_codes | ✔ | Array of service codes from /meta, e.g. ["AIR"] |
origin_country, origin_state, origin_address, origin_port | ✔ at least one of: country / address / port | Pickup point |
dest_country, dest_state, dest_address, dest_port, dest_postal_code | ✔ at least one of: country / address / port | Delivery point |
incoterm or delivery_scope | ✔ at least one | Delivery terms / scope of service |
delivery_required_date, pickup_date or transit_time_required | ✔ at least one | Time requirement |
cargo_ready_date, etd_expected | Cargo ready date, expected departure date | |
special_requirements | Array of special requirement codes | |
special_note | Additional notes | |
currency | Preferred quotation currency, e.g. USD | |
customer_tax_code, customer_address, customer_country | Invoicing details | |
cargos | ✔ | Cargo lines, 1 to 200 lines |
Each line in cargos:
| Field | Required | Description |
|---|---|---|
name | ✔ | Goods name |
package_count | ✔ | Number of packages, ≥ 1 |
gross_weight | ✔* | Total weight (kg) |
length, width, height, dimension_unit | ✔* | Dimensions of each package. dimension_unit: cm (default), m, mm, inch |
container_type, container_count | For full container loads. With container_count, weight and dimensions are not required | |
package_type, weight_per_package, description, hs_code, origin_country, nature, goods_value, goods_currency, temperature_requirement, note | Additional information | |
has_battery, has_liquid, has_chemical, is_dangerous, requires_permit, permit_note | Cargo characteristics (true/false) |
* Unless container_count is provided.
Example
POST /api/partner/fwd/orders
Authorization: Bearer eyJ0eXAiOi...
Content-Type: application/json
{
"partner_ref": "PO-2026-0917",
"contact_name": "John Smith",
"contact_email": "logistics@example.com",
"service_codes": ["AIR"],
"origin_country": "VN",
"origin_address": "Tan Binh Industrial Park, Ho Chi Minh City",
"dest_country": "US",
"dest_address": "Los Angeles, CA 90001",
"incoterm": "FOB",
"delivery_required_date": "2026-10-20",
"cargos": [
{
"name": "Cotton T-shirts",
"package_count": 10,
"gross_weight": 150,
"length": 60, "width": 40, "height": 40, "dimension_unit": "cm",
"hs_code": "6109.10",
"goods_value": 3200, "goods_currency": "USD"
}
]
}
Response 201: request details (see section 5). Store data.rfq_code for later calls.
If your account has not yet been assigned a Bell sales representative, the request is rejected with 422 and errors.sales_user_id. If this happens, please contact Bell.
5. Look up requests
GET /orders — list
| Query | Description |
|---|---|
status | Filter by status; accepts one or more values: status[]=quoted&status[]=confirmed |
partner_ref | Exact match on your reference |
search | Search by rfq_code, quotation number, order number, partner_ref |
from_date, to_date | Filter by creation date (YYYY-MM-DD) |
updated_since | Only requests changed since this time. Use it for periodic sync if you do not use webhooks |
page, per_page | Pagination; per_page up to 100, default 20 |
{
"data": {
"items": [
{
"rfq_code": "RFQ-HCM-2609-0012",
"partner_ref": "PO-2026-0917",
"quotation_no": "QT-HCM-2609-0005",
"job_number": null,
"status": "quoted",
"status_label": "Đã có báo giá",
"services": [ { "code": "AIR", "name": "Vận chuyển hàng không", "transport_mode": "air" } ],
"origin_country": "VN",
"dest_country": "US",
"created_at": "2026-09-17T09:00:00+07:00",
"updated_at": "2026-09-18T15:20:00+07:00"
}
],
"meta": { "page": 1, "per_page": 20, "total": 1, "last_page": 1 }
}
}
GET /orders/{rfq_code} — details
Includes the list fields, plus:
| Field | Description |
|---|---|
submitted_at | Time the request was sent |
info_request | What Bell needs you to provide. Only set when status = need_info |
operation_status, operation_status_label | Current operational progress |
is_on_hold | true if the shipment is on hold |
contact, origin, destination, schedule | Information you provided |
delivery_scope, incoterm, special_requirements, special_note, currency | |
cargos | Cargo lines, with values calculated by Bell: volume (m³), volumetric_weight, chargeable_weight |
quotation | Quotation summary (quotation_no, version, sent_at, valid_until), or null if there is none yet |
job_number is the order number Bell issues once the order is approved for execution.
PUT /orders/{rfq_code} — provide more information
Only allowed when status = need_info. Send only the fields that change, in the same format as when creating. If you send cargos or service_codes, the new list replaces the old list entirely. You can include a note for Bell. After a successful call, the request goes back to submitted.
{
"cargos": [ { "name": "Cotton T-shirts", "package_count": 10, "gross_weight": 150,
"length": 60, "width": 40, "height": 40, "hs_code": "6109.10" } ],
"note": "Added HS code as requested"
}
6. Quotation
GET /orders/{rfq_code}/quotation
Available once the request has a quotation; otherwise you receive 404. The figures match the quotation Bell sends by email.
{
"data": {
"quotation_no": "QT-HCM-2609-0005",
"version": 1,
"sent_at": "2026-09-18T15:20:00+07:00",
"valid_until": "2026-09-25",
"is_expired": false,
"currency": "USD",
"services": "Vận chuyển hàng không",
"route": "Hồ Chí Minh → Los Angeles",
"delivery_scope": "Door to Door",
"carrier": "Vietnam Airlines",
"schedule": "Thứ 2, 4, 6",
"transit_time": "3-4 ngày",
"inclusions": ["Cước hàng không", "Phí THC đầu xuất"],
"exclusions": ["Thuế nhập khẩu tại Mỹ"],
"risks": [],
"charges": [
{ "line_no": 1, "name": "Cước hàng không", "unit": "kg", "quantity": 160,
"unit_price": 4.2, "amount": 672, "currency": "USD",
"vat_rate": 0, "vat_amount": 0, "total": 672, "applied_condition": null }
],
"totals": { "amount": 672, "vat": 0, "total": 672 },
"terms": ["Báo giá chỉ có hiệu lực trong thời hạn ghi trên báo giá…"],
"can_respond": true
}
}
Text fields in the quotation (charge names, schedule, inclusions, terms...) are entered by Bell staff and are usually in Vietnamese. can_respond = true means you can still respond to this quotation.
GET /orders/{rfq_code}/quotation/document
Returns the quotation as HTML (text/html) for display, printing or saving as PDF.
Respond to a quotation
The three calls below are only allowed when can_respond = true. Each returns the updated request details.
| Call | Body | Result |
|---|---|---|
POST /orders/{rfq_code}/quotation/accept | { "note": "…" } (optional) | status → confirmed. An expired quotation cannot be accepted (422) |
POST /orders/{rfq_code}/quotation/negotiate | { "note": "Could you reduce the price by 5%…" } (required) | Bell receives the request and may send a new quotation version (version increases). status stays quoted |
POST /orders/{rfq_code}/quotation/reject | { "reason_code": "price_high", "note": "…" } (required) | status → cancelled |
reason_code accepts one of: price_high, chose_competitor, transit_time_unsuitable, service_not_met, customer_cancelled_plan, payment_terms_disagreement, commercial_terms_disagreement, other.
7. Shipment progress — GET /orders/{rfq_code}/trackings
List of progress milestones, newest first. Returns an empty array if the shipment has not started yet.
{
"data": [
{ "id": 881, "operation_status": "departed", "operation_status_label": "Đã khởi hành",
"happened_at": "2026-09-22T23:40:00+07:00", "location": "SGN", "content": "Chuyến bay VN 1234" }
]
}
8. Documents
GET /orders/{rfq_code}/attachments
Documents you have uploaded and documents Bell shares with you, such as the bill of lading (bill_of_lading), customs declaration (customs_declaration), proof of delivery (pod) and handover record (handover_record).
{ "data": [ { "id": 52, "doc_type": "bill_of_lading", "file_name": "AWB-738-1234.pdf",
"mime_type": "application/pdf", "file_size": 184233, "note": null,
"uploaded_at": "2026-09-22T10:00:00+07:00" } ] }
GET /orders/{rfq_code}/attachments/{id}/download
Returns a time-limited download link (presigned URL), not the file content. Send a GET to url to download the file, without the Authorization header. The link only works until expires_at (10 minutes by default); after that, call this endpoint again for a new link. Do not store url for later use.
{
"data": {
"url": "https://<bucket>.s3.<region>.amazonaws.com/fwd/123/AbC.pdf?X-Amz-Algorithm=...&X-Amz-Signature=...",
"expires_at": "2026-09-23T10:25:00+07:00",
"file_name": "AWB-738-1234.pdf"
}
}
POST /orders/{rfq_code}/attachments — upload a document
multipart/form-data:
| Field | Required | Description |
|---|---|---|
file | ✔ | pdf, jpg, jpeg, png, webp, doc, docx, xls, xlsx, csv, txt, zip; up to 20 MB |
doc_type | ✔ | commercial_invoice, packing_list, contract, purchase_order, cargo_photo, catalogue, msds, coa, permit, certificate_of_origin |
note | Note |
curl -X POST https://expressbe.bellvn.com/api/partner/fwd/orders/RFQ-HCM-2609-0012/attachments \
-H "Authorization: Bearer $TOKEN" \
-F "doc_type=commercial_invoice" -F "file=@invoice.pdf"
Closed requests (completed or cancelled) do not accept new documents.
9. Webhooks
If you register a webhook URL with Bell, the system sends a POST to that URL whenever an event occurs. Use webhooks instead of polling GET /orders. Webhooks share the URL, secret, signing method and retry policy with the courier shipment tracking webhooks.
Headers
Content-Type: application/json
User-Agent: BellVN-Webhook/1.0
X-Bell-Event: fwd.status_changed
X-Bell-Delivery: 12345
X-Bell-Signature: sha256=<hex>
- Signature verification: compute
HMAC-SHA256(raw_body, webhook_secret)as hex and compare it with the part aftersha256=. Use a constant-time comparison such ashash_equalsorcrypto.timingSafeEqual. - Response: return HTTP 2xx within 15 seconds. Otherwise Bell retries up to 5 times (after 10 seconds, 30 seconds, 2 minutes, 10 minutes, 30 minutes).
- Deduplication:
X-Bell-Deliverystays the same across retries, so use it as an idempotency key. - Event order: not guaranteed. If you need the exact current status, call
GET /orders/{rfq_code}.
fwd.status_changed
Sent when status (the status in section 2) changes. Internal processing steps at Bell that do not change status do not trigger a webhook.
{
"event": "fwd.status_changed",
"timestamp": "2026-09-18T15:20:03+07:00",
"data": {
"rfq_code": "RFQ-HCM-2609-0012",
"partner_ref": "PO-2026-0917",
"quotation_no": "QT-HCM-2609-0005",
"job_number": null,
"status": "quoted",
"status_label": "Đã có báo giá",
"previous_status": "quoting",
"changed_at": "2026-09-18T15:20:03+07:00"
}
}
fwd.tracking.created
Sent every time Bell records a shipment progress milestone.
{
"event": "fwd.tracking.created",
"timestamp": "2026-09-22T23:41:00+07:00",
"data": {
"rfq_code": "RFQ-HCM-2609-0012",
"partner_ref": "PO-2026-0917",
"quotation_no": "QT-HCM-2609-0005",
"job_number": "FWD-HCM-2026-09-0031",
"status": "in_transit",
"tracking": {
"id": 881, "operation_status": "departed", "operation_status_label": "Đã khởi hành",
"happened_at": "2026-09-22T23:40:00+07:00", "location": "SGN", "content": "Chuyến bay VN 1234"
}
}
}
10. Suggested integration flow
POST /orders (partner_ref) → store rfq_code
│
├─ webhook status = need_info → GET /orders/{rfq} (read info_request) → PUT /orders/{rfq}
├─ webhook status = quoted → GET /orders/{rfq}/quotation → accept | negotiate | reject
├─ status = confirmed → POST /orders/{rfq}/attachments (invoice, packing list…)
├─ webhook fwd.tracking.created → update progress in your system
└─ status = completed → GET /orders/{rfq}/attachments (POD, bill of lading)
For questions about accounts, tokens and webhook registration, please contact Bell's sales team.