Get API key
Guides / Labels & dispatch

Labels & dispatch

After POST /v1/shipments succeeds, Merdi books with the carrier and the label PDF URL appears on the shipment record. This page explains when label_pdf_url is populated, how to poll for it, and how the test mode differs.

When the label_pdf_url is populated

The create call runs quote → persist → dispatch synchronously. When dispatch returns successfully, dispatch.status becomes "dispatched" and label_pdf_url is populated with a presigned OSS URL.

In most cases dispatch completes inside the POST call itself — meaning the 201 response body already contains label_pdf_url. In rare cases (carrier API hiccup, internal retry pending) you'll see status="pending" + label_pdf_url=null; poll GET /v1/shipments/{id} until either status flips.

Polling pattern (only when pending)

Dispatch runs synchronously inside POST /api/v1/shipments (sub-second), so under normal conditions label_pdf_url is already present in the 201 create response — no polling needed. Only poll when dispatch.status comes back "pending" (rare: carrier API hiccup / internal retry). In that case: poll every 1s up to 30s. If still not dispatched at 30s it's almost certainly a carrier-side issue — read dispatch.status + dispatch.error, then POST cancel + try another service_tier.

bash
# Poll until label_pdf_url is non-null OR dispatch.status === "failed".
TN="IM-2026-0528-9ZNCP3-1"
for i in $(seq 1 30); do
  resp=$(curl -sS "https://merdiexpress.com/api/v1/shipments/$TN" \
    -H "Authorization: Bearer mk_live_***")
  url=$(echo "$resp" | jq -r '.data.label_pdf_url')
  status=$(echo "$resp" | jq -r '.data.dispatch_status')
  echo "$i: status=$status url=$url"
  if [[ "$url" != "null" || "$status" == "failed" ]]; then break; fi
  sleep 1
done

Fetching the PDF

label_pdf_url is a presigned OSS URL — fetch it directly, no Authorization header needed. Best practice: re-upload to your own object storage right after download. The presigned URL has a carrier-side expiry; once it lapses you need to fetch a fresh one from /v1/shipments/{id}.

bash
# Direct download — no auth header.
curl -fL "$url" -o "label-$TN.pdf"

# Or pipe through to your storage:
curl -fL "$url" | aws s3 cp - "s3://my-labels/$TN.pdf"

Which number do I use?

A shipment carries up to three numbers — don't mix them up:

FieldLooks likeUse it for
tracking_numberIM-2026-...Merdi's canonical number — use it against our API (GET /api/v1/shipments/{id}) and our buyer-facing tracking page.
waybill_numberYT...First-leg carrier waybill (e.g. Yun Express YT…), assigned at dispatch.
last_mile_tracking_number9214… (USPS)Final-mile carrier number (e.g. USPS). Once present, THIS is the number to push to eBay / Amazon and show the buyer — the delivering carrier's own tracker recognises it. Null until the last-mile leg is assigned.

Practical rule: prefer last_mile_tracking_number for marketplaces once it's non-null; fall back to waybill_number while it's still null.

Buyer-facing tracking page

Want to show buyers the progress? The public tracking page is at https://merdiexpress.com/track?no={tracking_number} (pass tracking_number, not waybill_number).

Test-mode behaviour

When you create with an mk_test_* key, the dispatch step is skipped entirely: no carrier API call, no wallet deduction, no label_pdf_url. The response shows dispatch = null.

Want to test the PDF-download flow in staging? Mock a 200-byte PDF (e.g. https://www.example-files.com/test.pdf) and stub your fetcher. We don't yet have a sandbox carrier that returns a fake label_pdf_url; coming in the next sprint.