# Delivery Predictions

Delivery Predictions tells you when a shipment is likely to arrive, for any combination of carrier and service you ask about. The prediction comes from ShipStation's delivery prediction model, which is built on historical transit data, rather than from the transit time the carrier quotes.

The endpoint answers a question about a **lane** — one origin and one destination. You send that lane once, list every carrier and service combination you want predicted for it, and get one prediction per combination.

A prediction is not a rate, not a shipment, and not a commitment: use it to set delivery expectations, and use [Rates](/rate-shopping) when you need a price.

## Requirements

- The `carrier_code` and `service_code` of each combination you want predicted. These are the same service codes you use when [rate shopping](/rate-shopping) and [creating labels](/create-labels).
- The country and postal code of both ends of the lane.
- All requests are authenticated with your API key in the `api-key` header. See [Security & Authentication](/authentication).


## Predict Delivery Dates

**POST /v2/delivery_predictions**

Predict delivery dates for one or more carrier and service combinations on a lane.

### Example Request & Response

`ship_date` is optional. When you omit it, today's date is used.

There is no cap you need to manage: if you ask about more combinations than the model accepts in a single call, the request is split up automatically and every combination is still answered for.

Each prediction in the response includes:

| Property | Description |
|  --- | --- |
| `ship_date` | The ship date the prediction was calculated for — the one you sent, or today if you sent none. |
| `delivery_date` | The predicted delivery date. |
| `confidence_level` | How much historical data the prediction rests on. See [Confidence levels](#confidence-levels). |
| `carrier_details` | The `carrier_code` and `service_code` the prediction belongs to. |


## Matching Predictions to What You Asked For

We don't guarantee the order predictions come back in, and they aren't echoed back verbatim — each one carries the carrier and service exactly as the model reports it. Match a prediction to a request entry on the `carrier_code` and `service_code` pair in `carrier_details`, not on position.

Listing the same combination twice does not produce two predictions; it is predicted once.

## Confidence Levels

| Value | Meaning |
|  --- | --- |
| `high` | The prediction is based on historical data for this carrier service. |
| `low` | Little or no historical data was available for this service, so the prediction is based on similar services. Treat it as a weaker signal. |


## Warnings

A combination you ask about is never in both arrays. One the model has no coverage for produces an entry in `warnings` instead of a prediction:


```json
{
  "delivery_predictions": [],
  "warnings": [
    {
      "carrier_details": {
        "carrier_code": "fedex",
        "service_code": "fedex_express_saver"
      },
      "message": "Carrier and service combination is unsupported."
    }
  ]
}
```

No coverage is not an error
A response where none of the requested combinations is supported is still a `200 OK`, with an empty `delivery_predictions` array and one warning per combination. Check `delivery_predictions` for content rather than relying on the status code alone.

## Error Handling

Errors are returned with the standard [error response body](/apis/openapi), which includes a `request_id` and an `errors` array. Each error carries an `error_type`, `error_code`, and `message`; validation errors also include `field_name` and `field_value`.

| Status | When it occurs | `error_code` |
|  --- | --- | --- |
| `400 Bad Request` | A required field is missing — `from_country_code`, `from_postal_code`, `to_country_code`, `to_postal_code`, or `delivery_options` — or an entry in `delivery_options` is missing its `carrier_code` or `service_code`. | `invalid_field_value` |
| `400 Bad Request` | `ship_date` is in the past. | `invalid_field_value` |
| `400 Bad Request` | A country code or postal code cannot be resolved to a location. | `invalid_field_value` |
| `500 Internal Server Error` | An unexpected error occurred while processing the request. | *varies* |


## Delivery Predictions and Carrier Estimates

The [Rates](/rate-shopping) endpoints return the carrier's own transit estimates — `delivery_days`, `estimated_delivery_date`, and `carrier_delivery_days`. Those come from the carrier's published service commitments.

A delivery prediction is a different kind of answer to a similar question: it is derived from how the service has actually performed on comparable lanes. The two can disagree, and neither replaces the other. Rates are the place to go when you need the price alongside the estimate; this endpoint is the place to go when you want the prediction on its own, without quoting a shipment.

The same prediction is also attached to Rates, Checkout Rates and Labels responses as a `delivery_prediction` object, so you do not need a second call to get it alongside a rate or a purchased label:

| Where | Endpoints |
|  --- | --- |
| Rates | `POST /v2/rates`, `POST /v2/rates/estimate`, `GET /v2/rates/{rate_id}`, `GET /v2/shipments/{shipment_id}/rates` |
| [Checkout Rates](/checkout-rates) | `POST /v2/checkout_rates/{config_id}/quotes`, `GET /v2/checkout_rates/quotes/{quote_id}`. Calculating rates returns a prediction only when the request includes a `ship_from` address. |
| Labels | `POST /v2/labels` and its variants (by shipment, rate or shipping rule), `POST /v2/labels/{label_id}/return`, `GET /v2/labels`, `GET /v2/labels/{label_id}`, `GET /v2/labels/external_shipment_id/{external_shipment_id}` |


That embedded object carries `delivery_date` and `confidence_level` only — the carrier and service are already the context of the rate or label it hangs off, so they are not repeated. One difference from this endpoint's own response is worth knowing:

- The object is **omitted entirely** when no prediction is available, rather than being reported as a warning. Labels responses have no other delivery estimate, so there is nothing to fall back on there; Rates responses still carry the carrier's own `delivery_days`, `estimated_delivery_date`, and `carrier_delivery_days`, which the prediction complements rather than replaces.


`POST /v2/rates/bulk` does not carry a prediction: its response contains no rates, only the identifiers to retrieve them with.