Shipping Zone API
A shipping zone is a set of countries, optionally narrowed to regions within a country (states, provinces, or any level a country enumerates — the keys an address stores, such as US-CA or VN-SG). Zones no longer own methods directly — they own provider attachments. Configuring shipping is three steps:
- Create a zone — the geography.
- Attach a provider to the zone (
core, or one supplied by an extension). - For the built-in
coreprovider, define methods and give each one a per-zone rate.
POST /api/shippingZones/{id}/methods, PATCH /api/shippingZones/{zone_id}/methods/{method_id} and DELETE /api/shippingZones/{zone_id}/methods/{method_id} no longer exist. Their replacements are the provider endpoints (/api/shippingZones/:zone_id/providers[...]) and the Core provider endpoints (/api/shippingProviders/core/methods and /api/shippingProviders/core/rates) documented below.
The calculation_type field is gone entirely — the shape of the cost is inferred from which cost field you set on a rate. condition_type accepts only "price", "weight" or null; the old "none" value is rejected.
Zones
Create a Shipping Zone
Creates a shipping zone. Only name is required by the payload schema, but the request is rejected with 400 At least one country is required unless the normalized country list is non-empty — so send countries (preferred) or the legacy country.
Creating a zone automatically attaches the built-in core provider to it.
| Field Name | Field Type | Required | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| name | string | Yes | ||||||||||||
| countries | array of string | No | ||||||||||||
| country | string | No | ||||||||||||
| regions | array of object | No | ||||||||||||
| ||||||||||||||
- cURL
- JavaScript
curl
-H "Accept: application/json"
-H "Authorization: Bearer <admin JWT token>"
--data-raw '<JSON DATA>'
https://<your domain>/api/shippingZones
fetch('https://<your domain>/api/shippingZones', {
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer <admin JWT token>'
},
body: <JSON DATA>
})
.then(response => response.json())
.then(data => {
if(data.error) {
// Handle the error
} else {
// Handle the data
}
})
.catch(error => {
// Handle the error
});
{
"data": {
"shipping_zone_id": 3,
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "North America"
}
}
A multi-country zone with region restrictions:
{
"name": "North America",
"countries": ["US", "CA"],
"regions": [
{ "country": "US", "key": "US-CA" },
{ "country": "US", "key": "US-OR" },
{ "country": "CA", "key": "CA-BC" }
]
}
Every key must be an active region key of its country (regions(country) in GraphQL lists them). A key the data has retired — for example a Vietnamese province absorbed in the 2025 merger — is rejected with 400. When a zone covers a country the store does not sell to, the zone is still saved and the response carries a warnings array naming the conflict; nothing is deleted or rewritten.
shipping_zone.country was droppedCountries live in the shipping_zone_country table and regions in shipping_zone_region (zone_id, country, level, region_key; unique per zone, country, level and key), both keyed by zone. The zone row itself carries only name, so the create response does not echo the geography back.
Update a Shipping Zone
Replaces the zone's name, countries and regions. Country and region rows are replaced wholesale, not merged — send the complete lists every time. The same "at least one country" rule applies.
| Field Name | Field Type | Required | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| name | string | Yes | ||||||||||||
| countries | array of string | No | ||||||||||||
| country | string | No | ||||||||||||
| regions | array of object | No | ||||||||||||
| ||||||||||||||
- cURL
- JavaScript
curl
-H "Accept: application/json"
-H "Authorization: Bearer <admin JWT token>"
--data-raw '<JSON DATA>'
https://<your domain>/api/shippingZones/{id}
fetch('https://<your domain>/api/shippingZones/{id}', {
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer <admin JWT token>'
},
body: <JSON DATA>
})
.then(response => response.json())
.then(data => {
if(data.error) {
// Handle the error
} else {
// Handle the data
}
})
.catch(error => {
// Handle the error
});
{
"data": {
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}
{id} is the zone uuid.
Delete a Shipping Zone
Permanently removes a shipping zone along with its country, region, provider-attachment and rate rows.
- cURL
- JavaScript
curl
-H "Accept: application/json"
-H "Authorization: Bearer <admin JWT token>"
https://<your domain>/api/shippingZones/{id}
fetch('https://<your domain>/api/shippingZones/{id}', {
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer <admin JWT token>'
}
})
.then(response => response.json())
.then(data => {
if(data.error) {
// Handle the error
} else {
// Handle the data
}
})
.catch(error => {
// Handle the error
});
{
"data": {
"shipping_zone_id": 3,
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "North America"
}
}
Providers, Methods and Rates
A zone on its own offers nothing. Shipping options come from providers attached to the zone, and — for the built-in Core provider — from the methods and rates configured beneath it.
Those endpoints have their own reference:
| What you want to do | Where |
|---|---|
| Attach, update or detach a provider on a zone | Shipping Provider API |
| Create, update or delete a Core method | Shipping Provider API |
| Create, update or delete a Core rate (flat, price-tiered or weight-tiered) | Shipping Provider API |
| Build a provider of your own | Shipping Provider Development |
Selecting a Method at Checkout
Once zones, providers, methods and rates exist, the storefront reads the available methods from GraphQL and applies one with POST /api/carts/:cart_id/shippingMethods. See the Shipping Method API.