Warehouse and 3PL Automation

This guide shows how to integrate Envia into a warehouse or third-party logistics (3PL) operation that processes dozens to hundreds of shipments per day. The focus is on automation, batching, and efficiency.

What you'll build

flowchart LR
  WMS["WMS / OMS exports orders"] --> Batch["Batch Label Creation"] --> Manifest["Create Manifest"] --> Pickup["Schedule Pickup"] --> Dashboard["Tracking Dashboard"]

Your warehouse automation will:

  1. Process orders in batches from your WMS/OMS
  2. Generate labels in bulk with error handling per shipment
  3. Consolidate labels into manifests for carrier handoff
  4. Schedule daily pickups automatically
  5. Monitor all shipments via webhooks

Architecture overview

WMS / OMS exports pending orders
  → Your backend validates each destination address
    → Envia GET /zipcode/{country}/{code} (Geocodes API)
      → Confirms postal code and returns city/state
  → Your backend creates labels in batch (with rate limiting)
    → Envia POST /ship/generate/ (one per order)
      → Returns tracking number + label PDF per order
  → Labels grouped by carrier into manifests
    → Envia POST /ship/manifest (one manifest per carrier)
  → Daily pickup scheduled per carrier
    → Envia POST /ship/pickup/ (one per carrier per day)
  → Envia webhooks feed your tracking dashboard with status updates

Setup

All code examples below use these base URLs and token. Set ENVIA_ENV=production when you are ready to go live.

// Configuration — defaults to sandbox
const ENVIA_BASE    = "https://api-test.envia.com";
const QUERIES_BASE  = "https://queries-test.envia.com";
const GEOCODES_BASE = "https://geocodes.envia.com";
const WAREHOUSE_ADDRESS = { /* your warehouse origin address */ };
// Set ENVIA_TOKEN as an environment variable (see Authentication guide)
# Configuration — defaults to sandbox
import os, requests
ENVIA_BASE    = "https://api-test.envia.com"
QUERIES_BASE  = "https://queries-test.envia.com"
GEOCODES_BASE = "https://geocodes.envia.com"
ENVIA_TOKEN   = os.environ["ENVIA_TOKEN"]
WAREHOUSE_ADDRESS = { }  # your warehouse origin address

Step 1 — Batch label creation

Process orders in a loop with per-shipment error handling. Do not let one failed order block the entire batch.

async function processBatch(orders) {
  var results = { success: [], failed: [] };

  for (var i = 0; i < orders.length; i++) {
    var order = orders[i];
    try {
      // Validate address first
      var geoUrl = GEOCODES_BASE + "/zipcode/" + order.destination.country + "/" + order.destination.postalCode;
      var geo = await fetch(geoUrl, {
        headers: { Authorization: "Bearer " + process.env.ENVIA_TOKEN }
      }).then(function (r) { return r.json(); });

      if (!geo.data) {
        results.failed.push({ order: order, error: "Invalid postal code" });
        continue;
      }

      // Generate label
      var label = await fetch(ENVIA_BASE + "/ship/generate/", {
        method: "POST",
        headers: {
          Authorization: "Bearer " + process.env.ENVIA_TOKEN,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          origin: WAREHOUSE_ADDRESS,
          destination: order.destination,
          packages: order.packages,
          shipment: { type: 1, carrier: order.carrier, service: order.service },
        }),
      }).then(function (r) { return r.json(); });

      if (label.data && label.data[0] && label.data[0].trackingNumber) {
        results.success.push({
          orderId: order.id,
          trackingNumber: label.data[0].trackingNumber,
          labelUrl: label.data[0].label,
        });
      } else {
        results.failed.push({ order: order, error: "Label creation failed" });
      }

      // Rate limiting: small delay between requests
      await new Promise(function (resolve) { setTimeout(resolve, 150); });
    } catch (err) {
      results.failed.push({ order: order, error: err.message });
    }
  }

  return results;
}

Step 2 — Create a manifest

After generating labels, consolidate them into a manifest. The manifest groups all tracking numbers for a single carrier pickup.

curl --request POST \
  --url https://api-test.envia.com/ship/manifest \
  --header "Authorization: Bearer $ENVIA_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "carrier": "dhl",
    "trackingNumbers": [
      "7520610403",
      "7520610404",
      "7520610405"
    ]
  }'

Step 3 — Schedule a daily pickup

Schedule one pickup per carrier per day. Reference the manifest tracking numbers.

curl --request POST \
  --url https://api-test.envia.com/ship/pickup/ \
  --header "Authorization: Bearer $ENVIA_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "origin": {
      "name": "Warehouse Operations",
      "phone": "+52 8180000000",
      "street": "Av. Industrial 500",
      "city": "Monterrey",
      "state": "NL",
      "country": "MX",
      "postalCode": "64000"
    },
    "shipment": {
      "type": 1,
      "carrier": "dhl",
      "pickup": {
        "weightUnit": "KG",
        "totalWeight": 125,
        "totalPackages": 45,
        "date": "2026-03-04",
        "timeFrom": 9,
        "timeTo": 17,
        "carrier": "dhl",
        "trackingNumbers": ["7520610403", "7520610404", "7520610405"],
        "instructions": "Loading dock B, ring bell"
      }
    }
  }'

Step 4 — Automate the daily workflow

Combine all steps into a single daily automation that runs via cron or your scheduler.

async function dailyShippingRun() {
  console.log("Starting daily shipping run...");

  // 1. Fetch pending orders from your WMS/OMS
  var orders = await fetchPendingOrders();
  console.log("Processing " + orders.length + " orders");

  // 2. Create labels in batch
  var results = await processBatch(orders);
  console.log("Labels: " + results.success.length + " success, " + results.failed.length + " failed");

  // 3. Create manifests (one per carrier)
  var manifests = await createDailyManifests(results.success);
  console.log("Manifests created: " + manifests.length);

  // 4. Schedule pickups
  for (var i = 0; i < manifests.length; i++) {
    await schedulePickup(manifests[i].carrier, manifests[i].trackingNumbers);
  }
  console.log("Pickups scheduled");

  // 5. Report failures for manual review
  if (results.failed.length > 0) {
    await reportFailures(results.failed);
  }

  return results;
}

3PL-specific considerations

ConcernRecommendation
Rate limitingAdd 100-150ms delay between API calls to avoid 429 errors.
Error recoveryLog failed orders for manual review. Do not block the batch.
Carrier cutoff timesCheck pickup options via Queries API for carrier-specific cutoff times.
Multiple warehousesUse different origin addresses per warehouse. Create separate manifests.
Balance monitoringMonitor account balance before batch runs to prevent 402 errors mid-batch.
Tracking dashboardUse webhooks to feed a real-time dashboard. See Webhooks Guide.

Related pages