Shippo vs EasyPost vs ShipEngine - A Developer's Integration Guide
Shippo, EasyPost, and ShipEngine all provide shipping API access but differ in authentication format, request schemas, and response structures. All three use API key authentication with different header formats, have distinct rate request and label purchase endpoints, and return rates in different response shapes. Here is what you need to know to integrate each one.
How does authentication differ between Shippo, EasyPost, and ShipEngine?
All three use API key authentication but with different header formats. Shippo uses a customShippoToken header, EasyPost uses HTTP Basic Auth with the key as username, and ShipEngine uses a custom API-Key header. You cannot reuse the same authentication setup across providers.
Shippo
Shippo uses a custom Authorization header format. Your requests need: Authorization: ShippoToken your_key_here. Test keys start with shippo_test_ and live keys start with shippo_live_, which makes it easy to validate key format before even making a request.
EasyPost
EasyPost uses HTTP Basic Auth with your API key as the username and an empty password. In practice this means base64-encoding your_key: (note the trailing colon) and passing it as the Authorization header. Most HTTP clients handle this automatically when you pass the key as the username.
ShipEngine
ShipEngine uses a custom header: API-Key: your_key_here. Straightforward, and consistent across all endpoints.
How do you fetch shipping rates from each provider?
Rate fetching is where the APIs diverge most. Each provider has a different endpoint, request body format, and response structure for the same operation. Shippo returns rates nested inside a shipment object, EasyPost returns them inline in the response, and ShipEngine nests them under a rate_response.rates path.
Shippo
You POST to /shipments with the origin, destination, and parcel details. Setting async: false returns rates synchronously in the same response. Rates come back nested inside the shipment object under a rates key.
EasyPost
EasyPost's endpoint is POST /v2/shipments. Rates are returned inline in the response under a rates array. The address and parcel objects are nested slightly differently from Shippo, so you'll need separate serializers.
ShipEngine
ShipEngine uses POST /v1/rates with a separate request body format that includes a shipment object and a rate_options object where you specify which carrier services to include. Rates come back in a rate_response.rates path.
How do you purchase labels through each API?
Label purchasing requires different endpoints and payloads for each provider. Shippo uses a separate transactions endpoint, EasyPost buys through the shipment resource, and ShipEngine creates labels directly from a rate ID. All three return a tracking number and label URL on success.
- Shippo: POST to
/transactionswith theratefield set to the rate'sobject_id. - EasyPost: POST to
/v2/shipments/:id/buywith therateobject containing the selected rate ID. - ShipEngine: POST to
/v1/labels/rates/:rate_idwith no additional body required.
How do you validate API keys for each provider?
All three providers have lightweight endpoints you can hit to verify a key is valid without performing a real shipping operation. Each returns a 200 status for valid keys and 401 for invalid ones, making key validation straightforward across all three.
- Shippo: GET
/carrier_accounts- 200 means valid, 401 means invalid. - EasyPost: GET
/v2/addresses- same pattern. - ShipEngine: GET
/v1/warehouses- same pattern.
Do you need to integrate all three providers separately?
If you're integrating all three, you're writing three sets of serializers, three authentication configurations, and three response normalizers. That's manageable for one integration, but if you're building a multi-tenant platform where your users bring their own provider keys, the surface area grows quickly.
RateShip handles all three integrations behind a single unified API. You call one endpoint, pass shipment details once, and get back normalized rates from every provider your user has connected. The authentication differences, request shape differences, and response differences are all handled for you.