Your First Shipment
This guide walks through creating a shipment from start to finish: validating the address, getting rates, purchasing a label, and tracking delivery.
Most developers complete this guide in under 5 minutes.
Prerequisites
- A FlexOps API key (get one from the Dashboard)
- An SDK installed, or
curlfor raw API calls
Step 1: Validate the destination address
Always validate addresses before creating shipments to avoid carrier surcharges and failed deliveries.
- cURL
- Node.js
- Python
- C#
- Ruby
- PHP
- Go
- Java
curl -X POST https://api.flexops.io/v1/addresses/validate \
-H "X-API-Key: fxk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"street1": "456 Oak Ave",
"city": "Portland",
"state": "OR",
"zip": "97201",
"country": "US"
}'
const result = await client.shipping.validateAddress({
street1: '456 Oak Ave',
city: 'Portland',
state: 'OR',
zip: '97201',
country: 'US',
});
console.log(result.standardized);
result = client.shipping.validate_address({
"street1": "456 Oak Ave",
"city": "Portland",
"state": "OR",
"zip": "97201",
"country": "US",
})
print(result["standardized"])
var result = await client.PostAsync<AddressValidationResponse>(
client.WsPath("shipping/addresses/validate"),
new { street1 = "456 Oak Ave", city = "Portland", state = "OR", zip = "97201", country = "US" });
Console.WriteLine(result.Standardized);
result = client.shipping.validate_address({
street1: '456 Oak Ave',
city: 'Portland',
state: 'OR',
zip: '97201',
country: 'US',
})
puts result['standardized']
$result = $client->shipping->validateAddress([
'street1' => '456 Oak Ave',
'city' => 'Portland',
'state' => 'OR',
'zip' => '97201',
'country' => 'US',
]);
echo $result->standardized;
result, err := client.Shipping.ValidateAddress(ctx, &flexops.Address{
Street1: "456 Oak Ave",
City: "Portland",
State: "OR",
Zip: "97201",
Country: "US",
})
fmt.Println(result.Standardized)
var result = client.post(client.wsPath("shipping/addresses/validate"),
Map.of("street1", "456 Oak Ave", "city", "Portland", "state", "OR", "zip", "97201", "country", "US"),
AddressValidationResponse.class);
System.out.println(result.getStandardized());
The API returns a standardized address with any corrections applied (e.g., ZIP+4 appended, typos fixed).
Step 2: Get rates
Compare rates from all configured carriers in a single call:
- cURL
- Node.js
- Python
- C#
- Ruby
- PHP
- Go
- Java
curl -X POST https://api.flexops.io/v1/rates \
-H "X-API-Key: fxk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"from": { "zip": "80202", "country": "US" },
"to": { "zip": "97201", "country": "US" },
"parcel": { "weightOz": 16, "lengthIn": 10, "widthIn": 8, "heightIn": 4 }
}'
const rates = await client.shipping.getRates({
fromZip: '80202',
toZip: '97201',
weight: 16,
weightUnit: 'oz',
});
rates.forEach(r =>
console.log(`${r.carrier} ${r.service}: $${r.rate} (${r.estimatedDays} days)`)
);
rates = client.shipping.get_rates({
"fromZip": "80202",
"toZip": "97201",
"weight": 16,
"weightUnit": "oz",
})
for rate in rates:
print(f"{rate['carrier']} {rate['service']}: ${rate['rate']} ({rate['estimatedDays']} days)")
var rates = await client.PostAsync<List<ShippingRate>>(
client.WsPath("shipping/rates"),
new { fromZip = "80202", toZip = "97201", weight = 16, weightUnit = "oz" });
foreach (var rate in rates)
Console.WriteLine($"{rate.Carrier} {rate.Service}: ${rate.Rate} ({rate.EstimatedDays} days)");
rates = client.shipping.get_rates({
fromZip: '80202',
toZip: '97201',
weight: 16,
weightUnit: 'oz',
})
rates.each do |rate|
puts "#{rate['carrier']} #{rate['service']}: $#{rate['rate']} (#{rate['estimatedDays']} days)"
end
$rates = $client->shipping->getRates([
'fromZip' => '80202',
'toZip' => '97201',
'weight' => 16,
'weightUnit' => 'oz',
]);
foreach ($rates as $rate) {
echo "{$rate->carrier} {$rate->service}: \${$rate->rate} ({$rate->estimatedDays} days)\n";
}
rates, err := client.Shipping.GetRates(ctx, &flexops.RateRequest{
FromZip: "80202",
ToZip: "97201",
Weight: 16,
WeightUnit: "oz",
})
for _, rate := range rates {
fmt.Printf("%s %s: $%.2f (%d days)\n", rate.Carrier, rate.Service, rate.Rate, rate.EstimatedDays)
}
var rates = client.post(client.wsPath("shipping/rates"),
Map.of("fromZip", "80202", "toZip", "97201", "weight", 16, "weightUnit", "oz"),
ShippingRate[].class);
for (var rate : rates) {
System.out.printf("%s %s: $%.2f (%d days)%n",
rate.getCarrier(), rate.getService(), rate.getRate(), rate.getEstimatedDays());
}
This returns rates from all configured carriers, sorted by price. A typical response:
{
"rates": [
{ "carrier": "usps", "service": "ground_advantage", "rate": 5.25, "estimatedDays": 5 },
{ "carrier": "usps", "service": "priority", "rate": 7.85, "estimatedDays": 2 },
{ "carrier": "fedex", "service": "ground", "rate": 8.42, "estimatedDays": 4 },
{ "carrier": "ups", "service": "ground", "rate": 9.10, "estimatedDays": 3 }
]
}
Step 3: Purchase a label
Pick the best rate and create the label:
- cURL
- Node.js
- Python
- C#
- Ruby
- PHP
- Go
- Java
curl -X POST https://api.flexops.io/v1/labels \
-H "X-API-Key: fxk_live_your_api_key" \
-H "Idempotency-Key: order-12345-label" \
-H "Content-Type: application/json" \
-d '{
"rateId": "rate_xyz789",
"from": {
"name": "FlexOps HQ",
"street1": "123 Main St",
"city": "Denver",
"state": "CO",
"zip": "80202",
"country": "US"
},
"to": {
"name": "Jane Doe",
"street1": "456 Oak Ave",
"city": "Portland",
"state": "OR",
"zip": "97201",
"country": "US"
},
"parcel": { "weightOz": 16, "lengthIn": 10, "widthIn": 8, "heightIn": 4 }
}'
const label = await client.shipping.createLabel({
carrier: 'USPS',
service: 'PRIORITY_MAIL',
fromAddress: { name: 'FlexOps HQ', street1: '123 Main St', city: 'Denver', state: 'CO', zip: '80202', country: 'US' },
toAddress: { name: 'Jane Doe', street1: '456 Oak Ave', city: 'Portland', state: 'OR', zip: '97201', country: 'US' },
parcel: { weight: 16, weightUnit: 'oz', length: 10, width: 8, height: 4 },
idempotencyKey: 'order-12345-label',
});
console.log(`Label: ${label.labelUrl}`);
console.log(`Tracking: ${label.trackingNumber}`);
label = client.shipping.create_label({
"carrier": "USPS",
"service": "PRIORITY_MAIL",
"fromAddress": {"name": "FlexOps HQ", "street1": "123 Main St", "city": "Denver", "state": "CO", "zip": "80202", "country": "US"},
"toAddress": {"name": "Jane Doe", "street1": "456 Oak Ave", "city": "Portland", "state": "OR", "zip": "97201", "country": "US"},
"parcel": {"weight": 16, "weightUnit": "oz", "length": 10, "width": 8, "height": 4},
"idempotencyKey": "order-12345-label",
})
print(f"Label: {label['labelUrl']}")
print(f"Tracking: {label['trackingNumber']}")
var label = await client.PostAsync<LabelResponse>(
client.WsPath("shipping/labels"),
new
{
carrier = "USPS",
service = "PRIORITY_MAIL",
fromAddress = new { name = "FlexOps HQ", street1 = "123 Main St", city = "Denver", state = "CO", zip = "80202", country = "US" },
toAddress = new { name = "Jane Doe", street1 = "456 Oak Ave", city = "Portland", state = "OR", zip = "97201", country = "US" },
parcel = new { weight = 16, weightUnit = "oz", length = 10, width = 8, height = 4 },
});
Console.WriteLine($"Label: {label.LabelUrl}");
Console.WriteLine($"Tracking: {label.TrackingNumber}");
label = client.shipping.create_label({
carrier: 'USPS',
service: 'PRIORITY_MAIL',
fromAddress: { name: 'FlexOps HQ', street1: '123 Main St', city: 'Denver', state: 'CO', zip: '80202', country: 'US' },
toAddress: { name: 'Jane Doe', street1: '456 Oak Ave', city: 'Portland', state: 'OR', zip: '97201', country: 'US' },
parcel: { weight: 16, weightUnit: 'oz', length: 10, width: 8, height: 4 },
idempotencyKey: 'order-12345-label',
})
puts "Label: #{label['labelUrl']}"
puts "Tracking: #{label['trackingNumber']}"
$label = $client->shipping->createLabel([
'carrier' => 'USPS',
'service' => 'PRIORITY_MAIL',
'fromAddress' => ['name' => 'FlexOps HQ', 'street1' => '123 Main St', 'city' => 'Denver', 'state' => 'CO', 'zip' => '80202', 'country' => 'US'],
'toAddress' => ['name' => 'Jane Doe', 'street1' => '456 Oak Ave', 'city' => 'Portland', 'state' => 'OR', 'zip' => '97201', 'country' => 'US'],
'parcel' => ['weight' => 16, 'weightUnit' => 'oz', 'length' => 10, 'width' => 8, 'height' => 4],
'idempotencyKey' => 'order-12345-label',
]);
echo "Label: {$label->labelUrl}\n";
echo "Tracking: {$label->trackingNumber}\n";
label, err := client.Shipping.CreateLabel(ctx, &flexops.CreateLabelRequest{
Carrier: "USPS",
Service: "PRIORITY_MAIL",
FromAddress: &flexops.Address{
Name: "FlexOps HQ", Street1: "123 Main St",
City: "Denver", State: "CO", Zip: "80202", Country: "US",
},
ToAddress: &flexops.Address{
Name: "Jane Doe", Street1: "456 Oak Ave",
City: "Portland", State: "OR", Zip: "97201", Country: "US",
},
Parcel: &flexops.Parcel{Weight: 16, WeightUnit: "oz", Length: 10, Width: 8, Height: 4},
IdempotencyKey: "order-12345-label",
})
fmt.Printf("Label: %s\n", label.LabelURL)
fmt.Printf("Tracking: %s\n", label.TrackingNumber)
var label = client.post(client.wsPath("shipping/labels"), Map.of(
"carrier", "USPS",
"service", "PRIORITY_MAIL",
"fromAddress", Map.of("name", "FlexOps HQ", "street1", "123 Main St", "city", "Denver", "state", "CO", "zip", "80202", "country", "US"),
"toAddress", Map.of("name", "Jane Doe", "street1", "456 Oak Ave", "city", "Portland", "state", "OR", "zip", "97201", "country", "US"),
"parcel", Map.of("weight", 16, "weightUnit", "oz", "length", 10, "width", 8, "height", 4)
), LabelResponse.class);
System.out.println("Label: " + label.getLabelUrl());
System.out.println("Tracking: " + label.getTrackingNumber());
Always include an Idempotency-Key when creating labels to prevent duplicate charges if a request is retried. Use your order ID as the key.
Step 4: Print and ship
Download the label PDF from the labelUrl in the response, print it on a 4x6" thermal label, and affix it to your package.
| Label format | Best for |
|---|---|
| PDF (default) | Standard printers |
| PNG | Web display / preview |
| ZPL | Thermal printers (Zebra, DYMO) |
Step 5: Track delivery
- cURL
- Node.js
- Python
- C#
- Ruby
- PHP
- Go
- Java
curl -X GET https://api.flexops.io/v1/tracking/9400111899223456789012 \
-H "X-API-Key: fxk_live_your_api_key"
const tracking = await client.shipping.track('9400111899223456789012');
console.log(`Status: ${tracking.status}`);
console.log(`ETA: ${tracking.estimatedDelivery}`);
tracking = client.shipping.track("9400111899223456789012")
print(f"Status: {tracking['status']}")
print(f"ETA: {tracking['estimatedDelivery']}")
var tracking = await client.GetAsync<TrackingResponse>(
client.WsPath("shipping/tracking/9400111899223456789012"));
Console.WriteLine($"Status: {tracking.Status}");
Console.WriteLine($"ETA: {tracking.EstimatedDelivery}");
tracking = client.shipping.track('9400111899223456789012')
puts "Status: #{tracking['status']}"
puts "ETA: #{tracking['estimatedDelivery']}"
$tracking = $client->shipping->track('9400111899223456789012');
echo "Status: {$tracking->status}\n";
echo "ETA: {$tracking->estimatedDelivery}\n";
tracking, err := client.Shipping.Track(ctx, "9400111899223456789012")
fmt.Printf("Status: %s\n", tracking.Status)
fmt.Printf("ETA: %s\n", tracking.EstimatedDelivery)
var tracking = client.get(
client.wsPath("shipping/tracking/9400111899223456789012"),
TrackingResponse.class);
System.out.println("Status: " + tracking.getStatus());
System.out.println("ETA: " + tracking.getEstimatedDelivery());
Or register a webhook to receive status updates automatically — no polling needed.
Next steps
- Rate Shopping — AI-powered carrier recommendations
- Batch Shipping — Ship up to 500 packages in one call
- International Shipping — Customs declarations and HS codes
- Test Data — Addresses and tracking numbers for sandbox testing