Skip to main content

Forwarding API

The API lets your system:

  1. Send requests for quotation (RFQ) for forwarding shipments (air, sea, road, customs...).
  2. Provide additional information when Bell asks for it.
  3. Receive quotations, then accept, ask to renegotiate, or reject them.
  4. Follow shipment progress and exchange documents.
  5. Receive webhooks when a request's status changes or progress is updated.

1. General information

Base URLhttps://expressbe.bellvn.com/api/partner/fwd
FormatJSON, UTF-8 (Content-Type: application/json), except document uploads, which use multipart/form-data
AuthenticationAuthorization: Bearer <token> issued by Bell. This is the same token as the courier Partner API, if you already have one
Rate limit120 requests per minute per customer by default. Above that you receive 429; see the Retry-After header
DatesDates 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.

Labels and messages

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."] }
}
HTTPMeaning
200 / 201Success (201 = a new request was created)
401Missing, expired, invalid or revoked token. Contact Bell for a new token
404The request does not exist or does not belong to you
422Invalid data, or the action is not allowed in the current status
429Rate limit exceeded
5xxError on Bell's side; you can retry

2. Request status

statusMeaningWhat you should do
submittedBell has received the requestWait
need_infoBell needs more information; details are in the info_request fieldCall PUT /orders/{rfq_code}
quotingBell is preparing the quotationWait
quotedA quotation is availableReview the quotation, then accept / negotiate / reject
confirmedYou have accepted; the order is being preparedSend documents if needed
in_transitIn transit; details in operation_statusFollow tracking
completedShipment completed
cancelledCancelled 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:

CatalogValues
incotermEXW, FCA, FOB, CFR, CIF, CPT, CIP, DAP, DPU, DDP, OTHER
delivery_scopedoor_cy, cy_cy, cy_door, door_door, door_cfs, cfs_cfs, cfs_door, airport_airport, door_airport, airport_door, warehouse_warehouse, other
special_requirementstime, vehicle, carrier, packing, insurance, customs, permit, ior, cod, timed_delivery, confidential, other
container_type20DC, 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

FieldRequiredDescription
partner_refrecommendedYour reference, up to 100 characters, unique within your account
contact_nameContact person
contact_phone, contact_email, contact_positionDefault to your account details if empty
service_codesArray of service codes from /meta, e.g. ["AIR"]
origin_country, origin_state, origin_address, origin_port✔ at least one of: country / address / portPickup point
dest_country, dest_state, dest_address, dest_port, dest_postal_code✔ at least one of: country / address / portDelivery point
incoterm or delivery_scope✔ at least oneDelivery terms / scope of service
delivery_required_date, pickup_date or transit_time_required✔ at least oneTime requirement
cargo_ready_date, etd_expectedCargo ready date, expected departure date
special_requirementsArray of special requirement codes
special_noteAdditional notes
currencyPreferred quotation currency, e.g. USD
customer_tax_code, customer_address, customer_countryInvoicing details
cargosCargo lines, 1 to 200 lines

Each line in cargos:

FieldRequiredDescription
nameGoods name
package_countNumber 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_countFor 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, noteAdditional information
has_battery, has_liquid, has_chemical, is_dangerous, requires_permit, permit_noteCargo 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

QueryDescription
statusFilter by status; accepts one or more values: status[]=quoted&status[]=confirmed
partner_refExact match on your reference
searchSearch by rfq_code, quotation number, order number, partner_ref
from_date, to_dateFilter by creation date (YYYY-MM-DD)
updated_sinceOnly requests changed since this time. Use it for periodic sync if you do not use webhooks
page, per_pagePagination; 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:

FieldDescription
submitted_atTime the request was sent
info_requestWhat Bell needs you to provide. Only set when status = need_info
operation_status, operation_status_labelCurrent operational progress
is_on_holdtrue if the shipment is on hold
contact, origin, destination, scheduleInformation you provided
delivery_scope, incoterm, special_requirements, special_note, currency
cargosCargo lines, with values calculated by Bell: volume (m³), volumetric_weight, chargeable_weight
quotationQuotation 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.

CallBodyResult
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:

FieldRequiredDescription
filepdf, jpg, jpeg, png, webp, doc, docx, xls, xlsx, csv, txt, zip; up to 20 MB
doc_typecommercial_invoice, packing_list, contract, purchase_order, cargo_photo, catalogue, msds, coa, permit, certificate_of_origin
noteNote
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 after sha256=. Use a constant-time comparison such as hash_equals or crypto.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-Delivery stays 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.