Get started · 8 min read
Getting Started
Get an API key, create a shipment, fetch rates and book it.
All requests must include an API key in the x-api-key header.
Get an API key
For testing (sandbox):
Go to one of the links below to create a Sendify test account. After signing up, go to Settings > API to create your API key:
For production:
Log in to your regular Sendify account and create your API key under Settings > API.
All example requests are using the sandbox URL https://app.dev.sendify.se/ and will require a sandbox api key.
Verify your API-Key
To check if your API key is valid for the environment, you can use the /status endpoint.
Example request
curl --location 'https://app.dev.sendify.se/external/v1/status' \
--header 'x-api-key: $YOUR_API_KEY'If your key is correct, you will receive this response
{
"status": "You've authenticated correctly",
"team": "TEAM_NAME"
}Creating Shipments
Once authenticated, you can create shipments using the POST /shipments endpoint.
Create Bookable Shipments:
- To enable stricter validation, set the
enable_bookable_validationflag totruein your request body. - If the flag is
trueand required data is missing, the API response will detail what information is needed to make the shipment bookable via the API.
Example request
curl --location 'https://app.dev.sendify.se/external/v1/shipments' \
--header 'x-api-key: $YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"enable_bookable_validation": true,
"from": {
"name": "Sendify AB",
"address": {
"address_line_1": "Östra Larmgatan 16",
"country_code": "SE",
"postal_code": "41107",
"city": "Göteborg"
},
"contact": {
"name": "Support Agent",
"phone": "0103303091",
"email": "contact@sendify.se"
}
},
"to": {
"name": "Sendify GmbH",
"address": {
"address_line_1": " Jungfernstieg 30",
"country_code": "DE",
"postal_code": "20354",
"city": "Hamburg"
},
"contact": {
"name": "Support Agent",
"phone": "030814088588",
"email": "kontact@sendify.de"
},
"is_private_individual": false
},
"reference_id": "Sendify Shipment",
"packages": [
{
"depth_cm": 20,
"height_cm": 20,
"width_cm": 20,
"weight_kg": 2,
"quantity": 1,
"description": "Swedish Fika",
"type": "PACKAGE",
"stackable": true
}
],
"system": "Sendify"
}'Example response
{
"id": "YOUR_SHIPMENT_ID",
"reference_id": "Sendify Shipment"
}Example failed validation response
{
"errors": {
"packages[0].type": ["The field is required."],
"packages[0].weight_kg": ["The field is required."],
"from.address.address_line_1": ["The field is required."],
"to.address.address_line_1": ["The field is required."],
"to.contact.email": ["The field must be a valid email address."]
}
}All shipments created via this endpoint will show up under the import page on the Sendify website.
Packages containing dangerous goods are marked with dangerous_goods_type (limited_quantity or adr). See How to Ship Dangerous Goods.
Shipping Rules
Shipping rules are team-level configurations that automatically filter or adjust the rates returned during a rate search. To see all rules for your team (both enabled and disabled), call GET /shipping-rules:
Example request
curl --location 'https://app.dev.sendify.se/external/v1/shipping-rules' \
--header 'x-api-key: $YOUR_API_KEY'Example response
{
"shipping_rules": [
{
"id": "abc123",
"name": "Exclude DHL",
"type": "exclude",
"category": "search",
"enabled": true
},
{
"id": "def456",
"name": "Prefer UPS for heavy shipments",
"type": "prefer",
"category": "search",
"enabled": true
},
{
"id": "ghi789",
"name": "Include own agreement rates",
"type": "include",
"category": "search",
"enabled": false
}
]
}By default, only enabled rules are applied when you fetch rates. You can target specific rules — including disabled ones — by passing their ids in the shipping_rule_ids field of the rates request — see below.
Getting Shipment Rates
After creating a bookable shipment, you can get available shipping rates using the POST /shipments/rates endpoint.
Here's what you need to know about the rates returned:
- Always Filtered: The rates shown are specifically for your shipment details and are ready to be booked.
- Add-ons: If you've specified required add-ons for your shipment, the API will only return rates from carriers that support those add-ons.
- Compatibility: Be aware that not all add-ons work together. Selecting multiple incompatible add-ons might result in no rates being found.
- Pricing: The price shown for each rate already includes the cost of any selected add-ons. The price you see is the final price for that rate.
- Unsure about Add-ons? If you're not sure which add-on combinations are compatible, we recommend experimenting with add-ons on the Sendify website. The user interface there shows how they work together.
Simple Rate Request:
The easiest way to request rates is by providing the shipment_id and the requested_pickup_time.
Example request
curl --location 'https://app.dev.sendify.se/external/v1/shipments/rates' \
--header 'x-api-key: $YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"shipment_id": "YOUR_SHIPMENT_ID",
"requested_pickup_time": "2025-04-18T10:00:00+01:00"
}'To apply only specific shipping rules, include the shipping_rule_ids field with the ids from GET /shipping-rules:
curl --location 'https://app.dev.sendify.se/external/v1/shipments/rates' \
--header 'x-api-key: $YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"shipment_id": "YOUR_SHIPMENT_ID",
"requested_pickup_time": "2025-04-18T10:00:00+01:00",
"shipping_rule_ids": ["abc123"]
}'If an id in shipping_rule_ids does not match any rule, the endpoint returns HTTP 400.
Example response
{
"rates": [
{
"price_rank": 1,
"shipment_id": "YOUR_SHIPMENT_ID",
"booking_token": "YOUR_BOOKING_TOKEN",
"carrier_name": "UPS Sweden",
"product_name": "UPS Standard",
"price": "127",
"currency": "SEK",
"transport_business_days_min": 2,
"transport_business_days_max": 2,
"expires_at": "2025-05-14T11:41:56Z",
"receiver_pays": false,
"own_agreement": false,
"require_delivery_service_point": false,
"required_documents": [],
"pickup": {
"cutoff_date": "2025-05-14",
"cutoff_time": "17:00:00",
"date": "2025-05-14",
"time_window_start": "13:00:00",
"time_window_end": "18:00:00"
},
"estimated_delivery": {
"earliest_date": "2025-05-16",
"time_window_start": "09:00:00",
"time_window_end": "23:30:00"
}
},
{
"price_rank": 2,
"shipment_id": "YOUR_SHIPMENT_ID",
"booking_token": "YOUR_BOOKING_TOKEN",
"carrier_name": "DHL Freight Sweden",
"product_name": "DHL Parcel Connect Plus",
"price": "132",
"currency": "SEK",
"transport_business_days_min": 2,
"transport_business_days_max": 4,
"expires_at": "2025-05-14T11:41:56Z",
"receiver_pays": false,
"own_agreement": false,
"require_delivery_service_point": false,
"required_documents": [],
"pickup": {
"cutoff_date": "2025-05-15",
"cutoff_time": "12:00:00",
"date": "2025-05-15",
"time_window_start": "07:00:00",
"time_window_end": "17:00:00"
},
"estimated_delivery": {
"earliest_date": "2025-05-19",
"time_window_start": "09:00:00",
"time_window_end": "17:00:00"
}
}
],
"warnings": [],
"applied_shipping_rules": [
{
"id": "abc123",
"name": "Exclude DHL",
"type": "exclude",
"category": "search",
"enabled": true
},
{
"id": "def456",
"name": "Prefer UPS for heavy shipments",
"type": "prefer",
"category": "search",
"enabled": true
}
]
}Warnings
Every rates response includes a warnings array. It lists carriers that could not be offered because the shipment's data does not satisfy their field requirements — for example, a sender name that exceeds a carrier's character limit. All issues for the same carrier product are grouped into one entry with multiple messages in its warnings array.
Each entry has three fields:
| Field | Description |
|---|---|
carrier_name | The carrier's display name |
carrier_product_code | The specific carrier product code that was excluded |
warnings | Array of messages describing what needs to be corrected |
The endpoint always returns HTTP 200. When at least one carrier is bookable, rates contains the available options and warnings lists the ones that were dropped. When all carriers are excluded, rates is empty and warnings contains the full list so you can see exactly what to fix. HTTP 400 is only returned when no rates were found and there are no warnings at all (e.g. the route is not supported).
Example response when a carrier is excluded
{
"rates": [{ "carrier_name": "DHL Freight Sweden", "...": "..." }],
"warnings": [
{
"carrier_name": "UPS Sweden",
"carrier_product_code": "ups_sweden_standard",
"warnings": ["Company name is too long, max 27 chars"]
}
]
}Example when a carrier has multiple validation issues
{
"rates": [],
"warnings": [
{
"carrier_name": "UPS Sweden",
"carrier_product_code": "ups_sweden_standard",
"warnings": [
"Company name is too long, max 27 chars",
"Contact name is too long, max 22 chars"
]
}
]
}To resolve warnings, update the shipment's sender or receiver details so they satisfy the carrier's constraints, then request rates again.
Booking the Shipment
Each rate returned by the /shipments/rates endpoint includes a booking_token.
To book the shipment with your chosen rate:
-
Identify the
booking_tokenfrom the rate you want to use. -
Send this token to the
POST /shipments/bookendpoint.
Example request
curl --location 'https://app.dev.sendify.se/external/v1/shipments/book' \
--header 'x-api-key: $YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{"booking_token": "YOUR_BOOKING_TOKEN"}'Example response
{
"shipment_id": "YOUR_SHIPMENT_ID",
"main_tracking_id": "CARRIER_TRACKING_ID"
}This confirms the booking and provides the shipment_id and the carrier's main_tracking_id.
Printing Labels
After booking, you can print shipment labels using the POST /shipments/print endpoint.
You need to specify which shipment(s) to print labels for and how you want to receive them.
- Example: Get Label URL (Default Layout)
This is the simplest way to get a label. The response will contain a URL to the PDF.
Example request
curl --request POST \
--url https://app.dev.sendify.se/external/v1/shipments/print \
--header 'Accept: application/json, application/pdf' \
--header 'Content-Type: application/json' \
--header 'x-api-key: $YOUR_API_KEY' \
--data '{
"shipment_ids": [
"YOUR_SHIPMENT_ID"
],
"document_type": "label",
"label_layout": "1x2",
"output_format": "url"
}'Example response:
{ "output_url": "OUTPUT_URL" }Tracking Shipments
Once a shipment is booked and in transit, you can retrieve tracking updates using the GET /shipments/{shipment_id}/tracking endpoint.
This endpoint returns a list of tracking events.
Carriers only start reporting once they have physically handled the shipment, which can be hours after booking. Until the first carrier event arrives, the endpoint returns a single ORDERED event dated to the booking, so the tracking url is always available. The same event is what the tracking_status field on the shipment carries in the meantime.
Tracking data is limited in our sandbox because we do not actually book shipments there. Hence, the only tracking available is the booked event shown below.
Example request
curl --location 'https://app.dev.sendify.se/external/v1/shipments/{YOUR_SHIPMENT_ID}/tracking' \
--header 'x-api-key: $YOUR_API_KEY'Example response
[
{
"ID": "1ZXXXXXXXXXXXXXXXX",
"url": "https://se.sendify-staging.com/tracking/{YOUR_SHIPMENT_ID}",
"status": "ORDERED",
"description": "Shipment booked with the carrier",
"location_name": "Gothenburg",
"created_at": "2025-05-14T11:17:29.754885247Z"
}
]A shipment that never received a carrier event and has since been canceled, or whose status was set by hand, returns an empty list instead — its status field is the source of truth in that case.
To set up pushing booking confirmations and tracking events to a webhook see Webhooks.
Need help? Contact api@sendify.com and include the X-Sendify-Request-ID header from the relevant request if available.