Skip to main content

Create multiple shipments asynchronously (NDJSON)

POST
/api/v1/create-shipment/bulk

Queues a background job that creates shipments from a Newline-Delimited JSON (NDJSON) file. Each line in the file is a single shipment object — blank lines are ignored.

The endpoint returns immediately with a jobId. Poll GET /api/create-shipment/bulk/status/{jobId} to track progress. Maximum 500 shipments per request.


How to upload​

Send a multipart/form-data request containing:

Form fieldRequiredDescription
shipments✅The .ndjson file. The server identifies it by field name shipments or by .ndjson file extension.
cmpService✅Service type: DELIVERY or REVERSE. Applied to all rows in the batch.
subAccountId❌UUID of the sub-account. Optional when authenticating via API key.
locationId❌Pickup location ID. When provided every shipment starts as AWAITING_PICKUP and a pickup request is auto-created. When absent shipments start as NEW.

NDJSON Row Schema — one JSON object per line​

Each line in the uploaded .ndjson file represents a single shipment. Blank lines and BOM-prefixed first lines are silently skipped. The file must be UTF-8 encoded.


✅ Required fields​

FieldTypeValidationDescription
consigneeNamestringmax 255Full name of the recipient / consignee.
consigneePhonestringE.164 — +[1-9]\d{6,14}Primary phone of the recipient. Example: +201001234567
governorateIdintegerâ‰Ĩ 1Destination governorate ID. Omit only if districtId is provided — the server auto-fills this from the district.
cityIdintegerâ‰Ĩ 1Destination city ID — drives branch routing. Omit only if districtId is provided.
addressText or structured partsstring / parts—Free-text delivery address. Optional when streetName or buildingNumber is present — the server builds addressText from the parts automatically.

🔑 District auto-resolution​

If only districtId is sent (without cityId/governorateId), the server resolves both values from the district record before validation, so no extra round-trip is needed from the client.


📐 Shipment dimensions​

FieldTypeDefaultDescription
weightnumber0Gross weight in kg. Chargeable = max(weight, W×H×L / 5000).
lengthnumber0Length in cm.
widthnumber0Width in cm.
heightnumber0Height in cm.
noOfPcsinteger1Total pieces (â‰Ĩ 1).

👤 Consignee contact details​

FieldTypeDescription
attentionTostring (max 255)Attention-to / care-of name.
consigneePhoneAltstring (E.164)Alternate phone.
consigneePhoneAlt2string (E.164)Second alternate phone.
consigneeEmailstring (max 255)Consignee email address.
nationalIdstring (max 50)National ID for identity-verified delivery.
districtIdintegerDestination district ID. When sent without cityId/governorateId both are resolved automatically.
consigneeNotesstringDelivery notes shown to the courier.
latitudenumber (max 6dp)GPS latitude.
longitudenumber (max 6dp)GPS longitude.

🏠 Structured address components​

Address resolution pyramid:

  1. If streetName or buildingNumber is present → the server builds addressText from the structured parts (auto-detects Arabic vs English) and ignores any addressText you sent.
  2. Otherwise → the provided addressText is used as-is.

newAddressText is independent — stored as a secondary address on the consignee and does not affect the pyramid.

FieldTypeDescription
streetNamestring (max 255)Street name — triggers pyramid when present.
buildingNumberstring (max 255)Building/house number — triggers pyramid when present.
floorstring (max 255)Floor number.
apartmentstring (max 255)Apartment / unit number.
landmarkNearbystring (max 255)Nearby landmark for courier guidance.
newAddressTextstringOptional secondary address. Stored independently on the consignee.

📋 Shipment metadata​

FieldTypeDescription
refstring (max 255)Your internal order or reference number.
packageTypePARCEL | DOCUMENTS | BULKYPackaging type — drives handling rules.
contentsstring (max 500)Description of the shipment contents.
itemValuenumber (â‰Ĩ 0)Declared value in EGP — used for insurance.
cashnumber (â‰Ĩ 0)Cash-on-delivery (COD) amount in EGP. Zero = no COD.
specialInstructionsstring (max 500)Special handling instructions.
productIdinteger (â‰Ĩ 1)Product type ID. Overridden by the form-level productId if that is supplied.
shipmentServiceIdsinteger[]Add-on service IDs. Fetch from GET /create-shipment/all-services.

🔑 AWB and backdating​

FieldTypeDescription
awbstringManual AWB override. Auto-generated when omitted. Duplicate AWBs are skipped and reported in the skipped array.

â†Šī¸ Return location​

FieldTypeDescription
returnLocationIdinteger (â‰Ĩ 1)Location to which the shipment is returned on delivery failure. Defaults to the sub-account warehouse. The server derives returnBranchId from this location automatically.

đŸ“Ļ Sub-packages breakdown​

Use packages when a single shipment contains multiple pieces with different dimensions:

"packages": [
{ "quantity": 2, "weight": 3.5, "length": 30, "width": 20, "height": 15 },
{ "quantity": 1, "weight": 0.8, "length": 20, "width": 10, "height": 10 }
]

🌍 International shipment data​

"createInternationalData": {
"purposeOfShipment": "COMMERCIAL_SAMPLE",
"hsCode": "6109.10",
"madeInId": 1
}

đŸšĢ Server-derived fields — do NOT include in the NDJSON​

FieldDerived from
businessLocationIdSub-account warehouse (or locationId form field).
returnBranchIdBranch of the resolved returnLocationId.
deliveryBranchIdBranch of cityId (DELIVERY) or warehouse branch (REVERSE).
currentBranchIdAlways null at creation.
fromBranchIdAlways null at creation.

âš ī¸ REVERSE shipment notes​

When cmpService = REVERSE (the form field — not per-row):

  • The customer's flat address data (streetName, buildingNumber, addressText, etc.) becomes an ad-hoc pickup origin (fromLocationId).
  • Consignee = the merchant — the sub-account name + warehouse address is stored as the consignee.
  • returnLocationId — should be the merchant warehouse (or omit to default to it).

📄 Minimal valid NDJSON examples​

DELIVERY — districtId only (server resolves city + governorate):

{"consigneeName":"Ahmed Hassan","consigneePhone":"+201001234567","districtId":1,"addressText":"15 Ø´Ø§ØąØš Ø§Ų„Ų†ØĩØąØŒ Ø´Ų‚ØŠ 4، Ų…Ø¯ŲŠŲ†ØŠ Ų†ØĩØą","weight":2.5,"length":30,"width":20,"height":15,"cash":250,"ref":"ORD-001","packageType":"PARCEL","contents":"Ų…Ų„Ø§Ø¨Øŗ","productId":1}

DELIVERY — cityId + structured address:

{"consigneeName":"Mariam Tarek","consigneePhone":"+201223334445","governorateId":2,"cityId":25,"streetName":"Ø´Ø§ØąØš Ø§Ų„ØŦŲ„Ø§ØĄ","buildingNumber":"18","floor":"3","apartment":"12","landmarkNearby":"ØŖŲ…Ø§Ų… Ų…ØŗØŦد Ø§Ų„Ų†ŲˆØą","weight":0.5,"cash":0,"ref":"ORD-002","packageType":"DOCUMENTS","productId":1}

REVERSE — with explicit return location:

{"consigneeName":"Khaled Mohamed","consigneePhone":"+201501112233","governorateId":1,"cityId":10,"addressText":"45 Ø´Ø§ØąØš Ø§Ų„Ų‡ØąŲ…ØŒ Ø§Ų„ØŦŲŠØ˛ØŠ","weight":1.5,"length":25,"width":15,"height":10,"cash":0,"ref":"RTN-001","packageType":"PARCEL","returnLocationId":7,"productId":1}

Request​

Responses​

201 Created

Job enqueued. Poll trackingUrl for progress.

400 Bad Request

Validation error — malformed JSON, missing required fields, invalid enum, or > 500 rows. The message array contains per-row error details.

401 Unauthorized

Unauthorized.

404 Not Found

Sub-account or pickup location not found.

500 Internal Server Error

Internal Server Error.