Get Shipping Rates
/api/v1/shipments/{shipmentId}/rates
Open shipping rates in the API Reference.
Request Rates for a Shipment created by Create an Order. This call has no request body and no Order-status precondition — it works as soon as the Shipment exists, regardless of the Order's current status.
See Rates for the public Rate-result model and its bounded identifier handoff.
- cURL
- JavaScript
- C#
- Python
- Java
curl -s -X POST "${MAILHUB_API_BASE_URL}/api/v1/shipments/${SHIPMENT_ID}/rates" \
-H "Authorization: Bearer ${MAILHUB_ACCESS_TOKEN}"
const ratesResponse = await fetch(`${process.env.MAILHUB_API_BASE_URL}/api/v1/shipments/${shipmentId}/rates`, {
method: 'POST',
headers: {Authorization: `Bearer ${accessToken}`},
});
const {data: rateResults} = await ratesResponse.json();
const selectedRateId = rateResults.rates[0]?.id;
var ratesRequest = new HttpRequestMessage(HttpMethod.Post, $"/api/v1/shipments/{shipmentId}/rates");
ratesRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var ratesResponse = await client.SendAsync(ratesRequest);
using var ratesPayload = JsonDocument.Parse(await ratesResponse.Content.ReadAsStreamAsync());
var rates = ratesPayload.RootElement.GetProperty("data").GetProperty("rates");
var selectedRateId = rates[0].GetProperty("id").GetString();
import requests
rates_response = requests.post(
f"{api_base_url}/api/v1/shipments/{shipment_id}/rates",
headers={"Authorization": f"Bearer {access_token}"},
)
rate_results = rates_response.json()["data"]
selected_rate_id = rate_results["rates"][0]["id"] if rate_results["rates"] else None
// Java 11+ java.net.http — no Mailhub package to install and no third-party client.
var request = HttpRequest.newBuilder()
.uri(URI.create(apiBaseUrl + "/api/v1/shipments/" + shipmentId + "/rates"))
.header("Authorization", "Bearer " + accessToken)
.POST(HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
// Parse response.body() with your own JSON library. Keep the id of the Rate your
// application chooses from data.rates, and check data.carrierResponses for the
// carriers that failed while others succeeded.
Response
{
"success": true,
"data": {
"totalCount": 2,
"carrierCount": 2,
"rates": [
{
"id": "c4a1b2d3-...",
"carrier": "USPS",
"serviceCode": "<carrier-specific service code>",
"serviceName": "USPS Priority Mail",
"rate": 8.42,
"currency": "USD",
"deliveryDays": 2,
"estDeliveryDays": 2,
"deliveryDateGuaranteed": false
},
{
"id": "d5b2c3e4-...",
"carrier": "UPS",
"serviceCode": "<carrier-specific service code>",
"serviceName": "UPS Ground",
"rate": 11.15,
"currency": "USD",
"deliveryDays": 4,
"estDeliveryDays": 4,
"deliveryDateGuaranteed": false
}
],
"carrierResponses": [
{"carrier": "USPS", "success": true, "errorCode": null, "errorMessage": null},
{"carrier": "UPS", "success": true, "errorCode": null, "errorMessage": null}
]
},
"errors": null
}
carrier, serviceCode, and serviceName above are illustrative example
values, not a fixed enum — exact carriers/services depend on your account,
sub-account, and environment configuration. serviceCode is carrier-specific
and its shape is not part of the public contract: pass it through, and do not
parse, hard-code, or switch on it. Select a service by matching serviceName
where you need to, and keep the returned Rate id for the purchase. listRate, retailRate,
billingType, mailClass, zone, and estimatedDeliveryDate are also
present on each rate when the carrier returns them; see the
API Reference for the complete field list.
The two arrays answer different questions, and a 200 can carry both:
rates[]holds the usable Rate options — every Rate returned, across all carriers that produced one. A carrier that failed contributes nothing here.carrierResponses[]is the per-carrier diagnostic view: one entry for every carrier that was asked, including the ones that returned nothing, so a carrier that declined can be told apart from one that was never contacted.carrierCountis the number of requested carriers that returned at least one Rate — not the number that answered. A carrier that replied with an error is recorded incarrierResponses[]but does not raisecarrierCount.totalCountis the number of entries inrates[].- A failed
carrierResponsesentry does not mean the operation failed. Treat a partial result as a normal outcome, and do not re-issue the request expecting the missing carrier to appear.
Continue with Select a Rate before
Buy a Label. Working with USPS? See
USPS with Mailhub.
Rates carry no expiry and no TTL. What invalidates a rateId is calling this
operation again for the same Shipment: that replaces the Shipment's Rate set, and the
previous identifiers stop resolving. Buy from the set you most recently requested. See
Validity and outcomes.
Errors
404 if the Shipment does not exist for your account. See
Rate Errors for general recovery guidance.