> ## Documentation Index
> Fetch the complete documentation index at: https://dev.phygitals.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Get shipping quote

> Returns shipping cost and ETA estimates for one or more items to a given destination.

Returns carrier rates and ETAs for shipping one or more items to a destination. Use this to display shipping options before the user commits.

<Note>
  **No payment is required.** Quotes are informational and shipping fees are settled B2B with the partner.
</Note>

## Request body

<ParamField body="item_ids" type="string[]" required>
  Item identifiers from [`/api/v1/vm/buy/status`](/api-reference/purchase/status) (`nfts[].id`) or [`/api/v1/inventory/:user_id`](/api-reference/inventory) (`items[].id`).
</ParamField>

<ParamField body="destination" type="object" required>
  Shipping address.

  <Expandable title="destination fields" defaultOpen />
</ParamField>

## Example request

```http theme={"dark"}
POST /api/v1/ship/quote
X-API-Key: <your_api_key>
Content-Type: application/json

{
  "item_ids": ["item_id_1", "item_id_2"],
  "destination": {
    "name": "Jane Doe",
    "line1": "123 Main St",
    "line2": "Apt 4B",
    "city": "Austin",
    "state": "TX",
    "postal_code": "78701",
    "country": "US",
    "phone": "+15125551234",
    "email": "jane@example.com"
  }
}
```

## Response

<ResponseExample>
  ```json 200 theme={"dark"}
  {
    "session_id": "quote_01HF8X2YJK9PMQ4ZRT7NVCB6WD",
    "expires_at": "2026-04-18T19:30:00.000Z",
    "quotes": [
      {
        "id": "quote_01HF8X2YJK9PMQ4ZRT7NVCB6WD_rate_standard",
        "carrier": "USPS",
        "service": "Ground Advantage",
        "amount": 5.95,
        "estimated_days_min": 3,
        "estimated_days_max": 5,
        "insured": false
      },
      {
        "id": "quote_01HF8X2YJK9PMQ4ZRT7NVCB6WD_rate_priority",
        "carrier": "USPS",
        "service": "Priority Mail",
        "amount": 9.75,
        "estimated_days_min": 1,
        "estimated_days_max": 3,
        "insured": true
      },
      {
        "id": "quote_01HF8X2YJK9PMQ4ZRT7NVCB6WD_rate_express",
        "carrier": "FedEx",
        "service": "2Day",
        "amount": 18.50,
        "estimated_days_min": 2,
        "estimated_days_max": 2,
        "insured": true
      }
    ]
  }
  ```
</ResponseExample>

<ResponseField name="session_id" type="string">
  Quote session identifier (groups the returned `quotes[]`).
</ResponseField>

<ResponseField name="expires_at" type="string">
  ISO timestamp after which all quotes in this session are no longer valid.
</ResponseField>

<ResponseField name="quotes[].id" type="string">
  Rate identifier. Pass as `quote_id` in [`/api/v1/ship/request`](/api-reference/shipping/request).
</ResponseField>

<ResponseField name="quotes[].carrier" type="string">
  Shipping carrier (for example `USPS`, `FedEx`).
</ResponseField>

<ResponseField name="quotes[].service" type="string">
  Carrier service level (for example `Priority Mail`, `2Day`).
</ResponseField>

<ResponseField name="quotes[].amount" type="number">
  Shipping cost in USD. Billed B2B to the partner.
</ResponseField>

<ResponseField name="quotes[].estimated_days_min" type="number">
  ETA in business days (lower bound).
</ResponseField>

<ResponseField name="quotes[].estimated_days_max" type="number">
  ETA in business days (upper bound).
</ResponseField>

<ResponseField name="quotes[].insured" type="boolean">
  Whether the rate includes carrier insurance.
</ResponseField>

## Errors

| Status | Body                                                                                 | Cause                                                                                                                                                                                                                                                                                 |
| ------ | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `{ "error": "item_ids is required" }`                                                | Missing or empty `item_ids`                                                                                                                                                                                                                                                           |
| `400`  | `{ "error": "Invalid destination address", "details": "...", "suggested": { ... } }` | Address validation failed. The body includes a human-readable `details` string and, when available, a `suggested` normalized address object (Google's correction). See [Address validation](#address-validation) below.                                                               |
| `400`  | `{ "error": "Item not found" }`                                                      | One or more `item_ids` are unknown or not owned by this partner's user                                                                                                                                                                                                                |
| `400`  | `{ "error": "No shippable items in request" }`                                       | All items are non-shippable                                                                                                                                                                                                                                                           |
| `422`  | `{ "error": "Country not supported" }`                                               | **Production:** destination country is not currently serviced. **[Sandbox](/enterprise-api/sandbox):** the `country` field is not a 2-character string. The sandbox does **not** check against an actual country allow-list, so any 2-character value (including `"ZZ"`) is accepted. |
| `500`  | `{ "error": "Failed to fetch shipping rates" }`                                      | Carrier rate lookup failed                                                                                                                                                                                                                                                            |

## Address validation

Destinations submitted to `/ship/quote` are validated server-side via the [Google Address Validation API](https://developers.google.com/maps/documentation/address-validation/overview) before any rate lookup runs. The validator runs against both production and sandbox.

**Pass criteria:**

* Required fields are present and non-empty: `name`, `line1`, `city`, `country`.
* `country` is exactly 2 characters (ISO 3166-1 alpha-2).
* Google returns `validationGranularity` of `PREMISE` or `SUB_PREMISE`. `PREMISE_PROXIMITY` is **rejected** — it indicates Google is uncertain about the exact location, which carriers misroute or refuse for graded-card-grade shipments.
* No address components are flagged as `unconfirmed`.

**Failure response shape:**

```json 400 theme={"dark"}
{
  "error": "Invalid destination address",
  "details": "Address contains unconfirmed components",
  "suggested": {
    "name": "Jane Doe",
    "line1": "123 Main Street",
    "line2": "Apt 4B",
    "city": "Austin",
    "state": "TX",
    "postal_code": "78701-1234",
    "country": "US"
  }
}
```

When Google can suggest a corrected address, it's returned under `suggested` so the partner can prompt the end user to confirm. `suggested` may be absent when validation fails on a structural check (e.g. missing `name`).

**Outage behavior:** if the Google API is unreachable or returns a non-2xx response, `/ship/quote` **fails closed** with `400 { "error": "Invalid destination address", "details": "Validation failed: internal error" }`. Same behavior applies if `GOOGLE_MAPS_API_KEY` is not configured server-side. Address validation is a hard dependency of `/ship/quote` — partners should retry with backoff on this specific `details` value rather than treat the address as invalid.

## Sandbox notes

In [sandbox](/enterprise-api/sandbox), every `ship/quote` call returns exactly three rates in this order:

| Service          | Carrier | Insured | Days | Amount formula (USD)     |
| ---------------- | ------- | ------- | ---- | ------------------------ |
| Ground Advantage | USPS    | no      | 3–5  | `5.95 + (n − 1) × 1.50`  |
| Priority Mail    | USPS    | yes     | 1–3  | `9.75 + (n − 1) × 2.00`  |
| 2Day             | FedEx   | yes     | 2–2  | `18.50 + (n − 1) × 3.00` |

Where `n = item_ids.length`. Quotes expire **15 minutes** after issuance.

Sandbox `session_id` format: `quote_<16-char uppercase base64url>`. Quote IDs follow `${session_id}_rate_${"standard"|"priority"|"express"}`.
