Shopify live checkout rates
Shopify live checkout rates
During checkout, Shopify asks EcartAPI for shipping rates. EcartAPI sends one POST to your quote URL and waits for your rates. Your JSON array becomes the shipping methods the buyer sees. Home delivery (dropOff) and pickup (pickup) are both rows in that same list.
You have 5 seconds. After that the request fails and Shopify receives no rates from you.
Request you receive
POST your quote URL with Content-Type: application/json.
The body is one quote: origin, destination, items, package, currency, and locale. Item prices are in major units. Weight is kilograms.
{
"origin": {
"name": null,
"company": "TechNest",
"street": "Vasconcelos 1400",
"city": "San Pedro",
"state": "NL",
"country": "MX",
"postalCode": "66236"
},
"destination": {
"name": "testing test",
"street": "Casa de los azulejos #234, Casa",
"city": "San Nicolas",
"state": "NL",
"country": "MX",
"postalCode": "34567"
},
"items": [
{
"name": "Prueba del checkout",
"quantity": "1",
"weight": "0",
"price": "4000",
"productId": "10306702442665",
"variantId": "50504275624105"
}
],
"package": {
"weight": "1",
"weightUnit": "KG",
"lengthUnit": "CM",
"dimensions": { "length": "17.1", "width": "17.1", "height": "17.1" }
},
"currency": "MXN",
"locale": "es-MX"
}Response you return
Answer HTTP 200 with a JSON array. Each element is one shipping method. An empty array is valid and means you have no rates. Do not wrap the array in { "rates": [] } — EcartAPI builds that object for Shopify after mapping.
No field is required. Unknown keys are ignored. If one element has a known field with the wrong type, that element is dropped and the rest are still shown.
type chooses the mapper. Comparison is case-insensitive.
type you send | Mapper |
|---|---|
pickup, PICKUP, Pickup | Pickup |
dropOff, DROPOFF, any other text, or omitted | Home delivery |
Pickup elements that include pickupPoint are limited to 6 per serviceId. Home-delivery elements are not limited.
Empty string, null, and a missing key are treated the same: that value is skipped and the next priority is used.
Home delivery example
{
"type": "dropOff",
"serviceId": "1",
"service": "FedEx Nacional Económico",
"serviceDescription": "Door to door",
"totalPrice": 119.87,
"currency": "MXN",
"phoneRequired": false,
"deliveryDate": {
"min": { "date": "2026-10-03", "time": "09:00" },
"max": { "date": "2026-10-04", "time": "18:00" }
}
}Pickup example
{
"type": "pickup",
"serviceId": "9",
"service": "Branch delivery",
"totalPrice": 80,
"currency": "MXN",
"phoneRequired": true,
"deliveryDate": {
"max": { "date": "2026-10-04", "time": "18:00" }
},
"pickupPoint": {
"pickupPointCode": "SUC-02",
"reference": "Entrega en Sucursal",
"address": {
"address": "Av. Insurgentes",
"number": "200",
"locality": "Roma Norte",
"city": "Ciudad de México",
"postalCode": "06700"
}
}
}date is YYYY-MM-DD. time is HH:mm. totalPrice is major units (80 or 119.87), not cents — "119.87" as a string is accepted. phoneRequired accepts true, false, "true", and "false".
Shared fields
These three fields use the same rule for home delivery and pickup.
Price → total_price
total_priceYour totalPrice | Shopify total_price |
|---|---|
119.87 or "119.87" | 11987 (two decimals, then ×100) |
80 | 8000 |
Missing, null, or not a number | 0 |
Currency → currency
currencyYour currency | Shopify currency |
|---|---|
A non-empty string, such as MXN | That string |
Missing, null, or "" | null |
Phone flag → phone_required
phone_requiredThe phone string is ignored. Only the boolean is mapped.
Your phoneRequired | Shopify phone_required |
|---|---|
true or "true" | true — checkout asks for a phone number |
false, "false", missing, or null | false |
Home delivery mapping
Shopify fields produced: service_name, service_code, description, total_price, currency, min_delivery_date, max_delivery_date, phone_required.
Title → service_name
service_name| Priority | Field | Used when | Shopify title |
|---|---|---|---|
| 1 | service | Not empty | The service text |
| 2 | serviceDescription | service is missing, null, or "" | The serviceDescription text |
| 3 | none | Both are empty | null |
service and serviceDescription can both be present. The title uses service. The second line can still use serviceDescription. If you send only serviceDescription, that text becomes both the title and the second line.
| You send | Title the buyer sees |
|---|---|
service: FedEx Nacional Económico, serviceDescription: Door to door | FedEx Nacional Económico |
service omitted, serviceDescription: Door to door | Door to door |
| Both omitted | No title |
Second line → description
descriptionThere is no fallback chain. The second line is only serviceDescription.
Your serviceDescription | Shopify description |
|---|---|
| Non-empty text | That text |
Missing, null, or "" | null |
If you also send deliveryDate.min.date and deliveryDate.max.date, Shopify shows its own delivery promise from those dates — for example "1 a 2 días hábiles" — instead of relying on this text.
Code → service_code
service_codeThis value is not shown to the buyer. Shopify sends it back when the buyer selects the method.
| Priority | Your serviceId | Shopify service_code |
|---|---|---|
| 1 | Any value other than missing, null, "", or 0. Numbers are turned into text | That text — 1 becomes "1" |
| 2 | Missing, null, "", or 0 | The text undefined |
Always send serviceId. Without it, the selected method comes back as undefined.
Delivery window
| Shopify field | Source | Written when | Format |
|---|---|---|---|
min_delivery_date | deliveryDate.min | min.date is not empty | YYYY-MM-DD HH:mm:00 when time is present. YYYY-MM-DD when time is missing |
max_delivery_date | deliveryDate.max | max.date is not empty | Same format |
If deliveryDate is missing, or a bound has no date, that Shopify field is omitted. min and max are independent — you can send only one.
| You send | Shopify receives |
|---|---|
min.date 2026-10-03, min.time 09:00 | min_delivery_date: 2026-10-03 09:00:00 |
max.date 2026-10-04, max.time 18:00 | max_delivery_date: 2026-10-04 18:00:00 |
max.date 2026-10-04, no time | max_delivery_date: 2026-10-04 |
No deliveryDate | Neither date field |
Pickup mapping
Shopify fields produced: service_name, service_code, description, total_price, currency, max_delivery_date, phone_required.
min_delivery_date is never set for pickup, even if you send deliveryDate.min.
Title → service_name
service_name| Priority | Field | Used when | Shopify title |
|---|---|---|---|
| 1 | pickupPoint.reference | Not empty | The branch name |
| 2 | service | reference is missing, null, or "" | The service text |
| 3 | serviceDescription | The two fields above are empty | The serviceDescription text |
| 4 | none | All three are empty | null |
| You send | Title the buyer sees |
|---|---|
reference: Entrega en Sucursal, service: Branch delivery | Entrega en Sucursal |
No reference, service: Branch delivery | Branch delivery |
No reference, no service, serviceDescription: Collect at store | Collect at store |
| None of the three | No title |
Second line → description
descriptionThe second line is the address only. serviceDescription is not used here. Opening hours in availability are not shown here.
Pieces are skipped when they are missing, null, or "". The remaining pieces stay in this order, joined with , :
| Order | Piece | Source | Rule |
|---|---|---|---|
| 1 | Street | address and number | Joined with a space. If one is empty, only the other is kept |
| 2 | Place | locality, otherwise city | locality wins when not empty. city is used only when locality is missing, null, or "" |
| 3 | Postal code | postalCode | Appended last |
If every piece is empty, description is null.
| Address you send | Shopify description |
|---|---|
Street Av. Insurgentes, number 200, locality Roma Norte, city Ciudad de México, postal code 06700 | Av. Insurgentes 200, Roma Norte, 06700 |
| Same, but no locality | Av. Insurgentes 200, Ciudad de México, 06700 |
| Street only | Av. Insurgentes |
| Postal code only | 06700 |
| No address object | null |
These address keys are accepted and ignored: street, country, province, state, latitude, longitude.
Code → service_code
service_code| Priority | Your pickupPoint.pickupPointCode | Shopify service_code |
|---|---|---|
| 1 | Not empty. Numbers are turned into text | That text — SUC-02 stays SUC-02 |
| 2 | Missing, null, or "" | null |
This is not the same rule as home delivery. A pickup without pickupPointCode becomes null, not undefined. serviceId is not the pickup code — it only caps pickup rows at 6 per service.
Delivery window → max_delivery_date
max_delivery_date| You send | Shopify receives |
|---|---|
deliveryDate.max.date and max.time | max_delivery_date: YYYY-MM-DD HH:mm:00 |
max.date without time | max_delivery_date: YYYY-MM-DD |
No max.date | max_delivery_date is omitted |
deliveryDate.min only | Nothing — minimum is ignored for pickup |
Same Shopify field, different source
| Shopify field | Home delivery source | Pickup source |
|---|---|---|
service_name | service, then serviceDescription | pickupPoint.reference, then service, then serviceDescription |
description | serviceDescription only | Address: street + number, locality or city, postalCode |
service_code | serviceId, or the text undefined | pickupPoint.pickupPointCode, or null |
min_delivery_date | deliveryDate.min | Not set |
max_delivery_date | deliveryDate.max | deliveryDate.max |
total_price | totalPrice in cents, or 0 | Same |
currency | currency, or null | Same |
phone_required | phoneRequired, or false | Same |
Fields that never reach checkout
| You may send | Why it does not appear |
|---|---|
carrier | Name of the carrier company. Not copied |
phone | Phone string. Use phoneRequired instead |
locale | Language of your own text. Text is copied as you sent it |
landedCostTotal | Not copied |
pickupPoint.availability | Opening hours. Not copied into the pickup description |
Address street, country, province, state, latitude, longitude | Not copied into the pickup description |
Timeout and error handling
| Your response | Result in Shopify |
|---|---|
| HTTP 200 + JSON array, within 5 s | Your rates, after the mapping above |
| HTTP 200 + empty array | No rates from you |
| HTTP 400 | No rates. Shopify receives No rates available |
| HTTP 200 whose body is an object, string, or anything other than an array | Same error, No rates available |
| Any other HTTP status, timeout, or network failure | Shopify receives { "rates": [] } |
| One array element has a known field with the wrong type | That element is omitted. Valid elements are returned |
Answer in under 5 seconds. A slow 200 is a failure, not a late success.
Updated about 22 hours ago