Skip to main content

Create an Order

POST/api/v1/orders Open create order in the API Reference.

Create an Order to begin the standard Order workflow. The request requires a fromAddress, a toAddress, and at least one entry in shipments — each shipment carries the parcel Mailhub will rate and label.

Use this operation when you want standard Order creation without rating during the create request. Use Create an Order with Rates when you want one Order created and its Shipments rated in the same request.

curl -s -X POST "${MAILHUB_API_BASE_URL}/api/v1/orders" \
-H "Authorization: Bearer ${MAILHUB_ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"fromAddress": {
"name": "Jane Sender",
"street1": "123 Main St",
"city": "Austin",
"state": "TX",
"zip": "78701",
"country": "US",
"phone": "5125550100"
},
"toAddress": {
"name": "John Recipient",
"street1": "456 Oak Ave",
"city": "Denver",
"state": "CO",
"zip": "80202",
"country": "US",
"phone": "3035550100"
},
"shipments": [
{ "parcel": { "weight": 16, "length": 10, "width": 8, "height": 4 } }
]
}'

The public request schema requires fromAddress, toAddress, and each shipment's parcel.weight. The current domestic validation also requires a name or company, street, city, state, postal code, country, and phone value; street2, email, and the remaining optional fields can be omitted. For a custom parcel, the current domestic validation expects positive length, width, and height; a recognized predefined package can supply dimensions.

Response

A successful call returns 201 with the created Order, including a server-assigned id for the Order and for each Shipment:

{
"success": true,
"data": {
"id": "5f2c1a10-2b3d-4e5f-8a9b-0c1d2e3f4a5b",
"status": "Created",
"isReturn": false,
"fromAddress": { "id": "...", "street1": "123 Main St", "city": "Austin", "state": "TX", "zip": "78701", "country": "US", "createdAt": "...", "updatedAt": "..." },
"toAddress": { "id": "...", "street1": "456 Oak Ave", "city": "Denver", "state": "CO", "zip": "80202", "country": "US", "createdAt": "...", "updatedAt": "..." },
"shipments": [
{
"id": "b91a2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"status": "Unknown",
"isReturn": false,
"parcel": { "id": "...", "weight": 16, "length": 10, "width": 8, "height": 4 },
"createdAt": "...",
"updatedAt": "..."
}
],
"createdAt": "...",
"updatedAt": "..."
},
"errors": null
}

Continue the workflow

Keep both identifiers: data.id (the Order) and data.shipments[0].id (the Shipment). The Shipment identifier is what Get Shipping Rates and Buy a Label require — rates and label purchase are not gated by the Order's status, so you can request rates immediately.

Validation failures

400 indicates the request could not be created as sent (for example, a missing required address field or an empty shipments array). 401 indicates the bearer token is missing or no longer usable — see Authentication.

Optional: scope to a sub-account

If your account uses sub-accounts, add X-SubAccount-Id: <sub-account-id> to tag the new Order (and its Shipments) with that sub-account. See Sub-account scoping.

No documented idempotency key or safe-retry contract exists for this operation — see Retries and Ambiguous Outcomes before retrying an ambiguous or failed create request.