Skip to content

POD API ​

Use the POD API from your backend when a shipment needs delivery evidence. The shipment-scoped manifest and its media endpoints use the same Bearer token with pod:read; EVT430 webhook media uses the composite webhook credential described below.

Open interactive diagram

POD API requests: query shipment POD by tracking number, then optionally fetch a media URL returned in the manifest.

Text equivalent: your server sends GET shipment POD with a pod:read Bearer token; M Express returns the manifest and available media URLs. If media is needed, your server fetches a returned URL with the same token and receives photo or signature bytes. The diagram does not prescribe a browser, proxy, or other customer application architecture.

Keep the token on your server. If you display media in a browser, see the optional display example below; EVT430 media uses the separate credential described in its own section.

Endpoints ​

MethodPathPurpose
GET/client/shipments/{tracking_number}/pod/Return the shipment-scoped manifest.
GET/client/shipments/{tracking_number}/pod/{pod_id}/photos/{photo_id}/file/Return one validated photo.
GET/client/shipments/{tracking_number}/pod/{pod_id}/signature/file/Return one validated signature representation.

Prefix each path with https://mexpress.nz/api/v1 and send Authorization: Bearer <server-token>. The token must have pod:read. Never place it in browser code, a URL, HTML, logs, mobile storage, or a CDN.

Read media from an EVT430 webhook ​

When a subscribed endpoint receives pod.bundle_available / EVT430, the payload contains one to three verified photo links and may contain one verified signature link. Those links use the /client/webhook-pods/{pod_id}/... route family and are fetched by your server with:

http
Authorization: Bearer <signing_key_id>.<signing_secret>

The credential is derived from the current endpoint signing_key_id and signing_secret. Rotation, endpoint deactivation, or endpoint deletion invalidates the prior credential. Validate the link from the frozen event and never expose this credential to the browser or accept an arbitrary URL.

Read a manifest ​

js
export async function fetchPodManifest(
  trackingNumber,
  { token, baseUrl = API_BASE_URL, fetchImpl = globalThis.fetch } = {},
) {
  if (!token) throw new Error("A pod:read bearer token is required.");
  const response = await fetchImpl(
    endpoint(baseUrl, `/client/shipments/${encodeURIComponent(trackingNumber)}/pod/`),
    {
      method: "GET",
      headers: { Accept: "application/json", Authorization: `Bearer ${token}` },
    },
  );
  if (response.status === 200) return await json(response);
  if (response.status === 403) throw new Error("POD access was forbidden.");
  if (response.status === 404) throw new Error("POD manifest was not found.");
  throw new Error(`POD manifest request failed with HTTP ${response.status}.`);
}

function manifestMedia(manifest) {
  return (manifest.delivery_batches ?? [])
    .flatMap((batch) => [
      ...(batch.photos ?? []),
      ...(batch.signature ? [batch.signature] : []),
    ])
    .filter((media) => media && typeof media.url === "string");
}

export function mediaKeyFromManifestUrl(url, { baseUrl = API_BASE_URL } = {}) {
  const parsed = new URL(url, baseUrl);
  return `${parsed.pathname}${parsed.search}`;
}

export async function fetchPodMedia({
  trackingNumber,
  manifest,
  mediaKey,
  token,
  baseUrl = API_BASE_URL,
  fetchImpl = globalThis.fetch,
} = {}) {
  if (!token) throw new Error("A pod:read bearer token is required.");
  if (!manifest || !mediaKey || typeof mediaKey !== "string") {
    throw new Error("mediaKey must come from the POD manifest.");
  }
  const media = manifestMedia(manifest).find(
    (item) => mediaKeyFromManifestUrl(item.url, { baseUrl }) === mediaKey,
  );
  if (!media) throw new Error("mediaKey is not present in the current POD manifest.");
  const base = new URL(baseUrl);
  const expectedPrefix = `${base.pathname.replace(/\/$/, "")}/client/shipments/${encodeURIComponent(
    trackingNumber,
  )}/pod/`;
  const selected = new URL(mediaKey, base);
  if (
    selected.origin !== base.origin ||
    !selected.pathname.startsWith(expectedPrefix)
  ) {
    throw new Error("mediaKey is outside the current shipment POD prefix.");
  }
  const response = await fetchImpl(selected.toString(), {
    method: "GET",
    headers: { Authorization: `Bearer ${token}` },
  });
  if (response.status === 403) throw new Error("POD media access was forbidden.");
  if (response.status === 404) throw new Error("POD media was not found.");
  if (response.status !== 200) {
    throw new Error(`POD media request failed with HTTP ${response.status}.`);
  }
  const manifestContentType = media.content_type;
  if (!ALLOWED_POD_MEDIA_TYPES.has(manifestContentType)) {
    throw new Error("POD media manifest Content-Type is not allowed.");
  }
  const rawContentType = header(response.headers, "Content-Type");
  if (!rawContentType) {
    throw new Error("POD media response omitted Content-Type.");
  }
  const contentType = rawContentType.split(";", 1)[0].trim().toLowerCase();
  if (!ALLOWED_POD_MEDIA_TYPES.has(contentType)) {
    throw new Error("POD media response Content-Type is not allowed.");
  }
  if (contentType !== manifestContentType) {
    throw new Error("POD media Content-Type does not match manifest.");
  }
  return {
    body: new Uint8Array(await response.arrayBuffer()),
    contentType,
    cacheControl: "private, no-store",
    referrerPolicy: "no-referrer",
    contentTypeOptions: "nosniff",
    headers: {
      "Content-Type": contentType,
      "Cache-Control": "private, no-store",
      "Referrer-Policy": "no-referrer",
      "X-Content-Type-Options": "nosniff",
    },
  };
}
py
def fetch_pod_manifest(tracking_number: str, *, token: str, client, base_url: str = API_BASE_URL):
    if not token:
        raise ValueError("A pod:read bearer token is required.")
    response = client.get(
        _url(base_url, f"/client/shipments/{quote(tracking_number, safe='')}/pod/"),
        headers={"Accept": "application/json", "Authorization": f"Bearer {token}"},
    )
    if response.status_code == 200:
        return response.json()
    if response.status_code == 403:
        raise PermissionError("POD access was forbidden.")
    if response.status_code == 404:
        raise LookupError("POD manifest was not found.")
    raise RuntimeError(f"POD manifest request failed with HTTP {response.status_code}.")


def _manifest_media(manifest):
    media = []
    for batch in manifest.get("delivery_batches", []):
        media.extend(batch.get("photos", []))
        if batch.get("signature"):
            media.append(batch["signature"])
    return [item for item in media if isinstance(item.get("url"), str)]


def media_key_from_manifest_url(url: str, *, base_url: str = API_BASE_URL) -> str:
    parsed = urlparse(urljoin(base_url, url))
    return parsed.path + (f"?{parsed.query}" if parsed.query else "")


def fetch_pod_media(
    *,
    tracking_number: str,
    manifest,
    media_key: str,
    token: str,
    client,
    base_url: str = API_BASE_URL,
):
    if not token:
        raise ValueError("A pod:read bearer token is required.")
    if not isinstance(media_key, str) or not media_key:
        raise ValueError("mediaKey must come from the POD manifest.")
    media = next(
        (
            item
            for item in _manifest_media(manifest)
            if media_key_from_manifest_url(item["url"], base_url=base_url)
            == media_key
        ),
        None,
    )
    if media is None:
        raise ValueError("mediaKey is not present in the current POD manifest.")
    base = urlparse(base_url)
    selected = urlparse(urljoin(base_url, media_key))
    expected_prefix = (
        f"{base.path.rstrip('/')}/client/shipments/"
        f"{quote(tracking_number, safe='')}/pod/"
    )
    if (
        selected.scheme != base.scheme
        or selected.netloc != base.netloc
        or not selected.path.startswith(expected_prefix)
    ):
        raise ValueError("mediaKey is outside the current shipment POD prefix.")
    response = client.get(selected.geturl(), headers={"Authorization": f"Bearer {token}"})
    if response.status_code == 403:
        raise PermissionError("POD media access was forbidden.")
    if response.status_code == 404:
        raise LookupError("POD media was not found.")
    if response.status_code != 200:
        raise RuntimeError(f"POD media request failed with HTTP {response.status_code}.")
    manifest_content_type = media.get("content_type")
    if manifest_content_type not in ALLOWED_POD_MEDIA_TYPES:
        raise ValueError("POD media manifest Content-Type is not allowed.")
    raw_content_type = _header(response, "Content-Type")
    if raw_content_type is None:
        raise ValueError("POD media response omitted Content-Type.")
    content_type = raw_content_type.split(";", 1)[0].strip().lower()
    if content_type not in ALLOWED_POD_MEDIA_TYPES:
        raise ValueError("POD media response Content-Type is not allowed.")
    if content_type != manifest_content_type:
        raise ValueError("POD media Content-Type does not match manifest.")
    return {
        "body": response.content,
        "content_type": content_type,
        "cache_control": "private, no-store",
        "referrer_policy": "no-referrer",
        "content_type_options": "nosniff",
        "headers": {
            "Content-Type": content_type,
            "Cache-Control": "private, no-store",
            "Referrer-Policy": "no-referrer",
            "X-Content-Type-Options": "nosniff",
        },
    }

The response always has required tracking_number (string), pod_status (pending, partial, or complete), and delivery_batches (possibly empty array). pending means no current package has a completion time; partial means some but not all packages do; complete means all do.

For MX123456789, use this synthetic two-batch shape to test mapping and null handling:

json
{
  "tracking_number": "MX123456789",
  "pod_status": "partial",
  "delivery_batches": [
    {
      "pod_id": "11111111-1111-4111-8111-111111111111",
      "pod_completed_at": "2026-07-02T03:04:05.000000Z",
      "signer_name": "A. Recipient",
      "location": {
        "status": "captured",
        "latitude": "-36.8484600",
        "longitude": "174.7633320",
        "accuracy_meters": "4.50"
      },
      "packages": [
        {
          "tracking_number": "MX123456789",
          "sequence_number": 1
        }
      ],
      "photos": [
        {
          "photo_id": "33333333-3333-4333-8333-333333333333",
          "url": "/api/v1/client/shipments/MX123456789/pod/11111111-1111-4111-8111-111111111111/photos/33333333-3333-4333-8333-333333333333/file/",
          "content_type": "image/png"
        }
      ],
      "signature": {
        "url": "/api/v1/client/shipments/MX123456789/pod/11111111-1111-4111-8111-111111111111/signature/file/",
        "content_type": "image/svg+xml"
      }
    },
    {
      "pod_id": "22222222-2222-4222-8222-222222222222",
      "pod_completed_at": "2026-07-01T03:04:05.000000Z",
      "signer_name": null,
      "location": {
        "status": "unavailable",
        "latitude": null,
        "longitude": null,
        "accuracy_meters": null
      },
      "packages": [
        {
          "tracking_number": "MX123456789",
          "sequence_number": 2
        }
      ],
      "photos": [],
      "signature": null
    }
  ]
}

pod_id and photo_id are UUID strings. pod_completed_at is a nullable UTC date-time; signer_name and signature are nullable. Every batch contains required location, packages, and photos; those arrays may be empty. Batches are newest first by server completion time, then creation time. A same-Client shared batch can appear for every member shipment with the same POD identity and media bytes; packages and media URL prefixes are filtered to the queried shipment.

location.status is one of captured, failed, unavailable, or not_attempted. For captured, latitude, longitude, and accuracy_meters are persisted Decimal strings only when all three values are present, finite, and within the existing bounds. Missing, invalid, out-of-range, and non-captured values return all three as null; location evidence, source, metadata, and current driver position remain internal.

This shipment-scoped manifest always includes its required location object and uses null coordinates when values are unavailable. EVT430 has a separate optional top-level location: it is present only for a completed batch with captured status and complete valid values, and is omitted otherwise. Do not infer EVT430 omission rules from this manifest response.

packages maps the requested shipment tracking_number to its positive integer sequence_number from the related Package. It never exposes a scan code. photos contains photo_id, a manifest-derived url, and content_type (image/png or image/jpeg). signature contains a manifest-derived url and content_type (image/svg+xml, image/png, or image/jpeg), or is null. Raw signature strokes are never returned.

Optional: display media in a browser ​

If your application displays POD media in a browser, the following safe-proxy example keeps the M Express token on your server. This is a display example, not an additional POD API step.

The browser should send an application-owned mediaKey to your authenticated backend. Your backend must select the matching photo or signature from the current manifest, derive the URL itself, verify that its origin and path remain under /api/v1/client/shipments/{tracking_number}/pod/, and fetch it with the same token. Do not accept a browser-supplied upstream URL.

The imported POD region is the canonical safe-proxy example. Your application authenticates its own route, accepts an app-owned mediaKey, resolves that key against the current manifest, validates that the resulting URL remains under the current shipment's POD prefix, and never accepts a browser-supplied upstream URL.

js
export async function fetchPodManifest(
  trackingNumber,
  { token, baseUrl = API_BASE_URL, fetchImpl = globalThis.fetch } = {},
) {
  if (!token) throw new Error("A pod:read bearer token is required.");
  const response = await fetchImpl(
    endpoint(baseUrl, `/client/shipments/${encodeURIComponent(trackingNumber)}/pod/`),
    {
      method: "GET",
      headers: { Accept: "application/json", Authorization: `Bearer ${token}` },
    },
  );
  if (response.status === 200) return await json(response);
  if (response.status === 403) throw new Error("POD access was forbidden.");
  if (response.status === 404) throw new Error("POD manifest was not found.");
  throw new Error(`POD manifest request failed with HTTP ${response.status}.`);
}

function manifestMedia(manifest) {
  return (manifest.delivery_batches ?? [])
    .flatMap((batch) => [
      ...(batch.photos ?? []),
      ...(batch.signature ? [batch.signature] : []),
    ])
    .filter((media) => media && typeof media.url === "string");
}

export function mediaKeyFromManifestUrl(url, { baseUrl = API_BASE_URL } = {}) {
  const parsed = new URL(url, baseUrl);
  return `${parsed.pathname}${parsed.search}`;
}

export async function fetchPodMedia({
  trackingNumber,
  manifest,
  mediaKey,
  token,
  baseUrl = API_BASE_URL,
  fetchImpl = globalThis.fetch,
} = {}) {
  if (!token) throw new Error("A pod:read bearer token is required.");
  if (!manifest || !mediaKey || typeof mediaKey !== "string") {
    throw new Error("mediaKey must come from the POD manifest.");
  }
  const media = manifestMedia(manifest).find(
    (item) => mediaKeyFromManifestUrl(item.url, { baseUrl }) === mediaKey,
  );
  if (!media) throw new Error("mediaKey is not present in the current POD manifest.");
  const base = new URL(baseUrl);
  const expectedPrefix = `${base.pathname.replace(/\/$/, "")}/client/shipments/${encodeURIComponent(
    trackingNumber,
  )}/pod/`;
  const selected = new URL(mediaKey, base);
  if (
    selected.origin !== base.origin ||
    !selected.pathname.startsWith(expectedPrefix)
  ) {
    throw new Error("mediaKey is outside the current shipment POD prefix.");
  }
  const response = await fetchImpl(selected.toString(), {
    method: "GET",
    headers: { Authorization: `Bearer ${token}` },
  });
  if (response.status === 403) throw new Error("POD media access was forbidden.");
  if (response.status === 404) throw new Error("POD media was not found.");
  if (response.status !== 200) {
    throw new Error(`POD media request failed with HTTP ${response.status}.`);
  }
  const manifestContentType = media.content_type;
  if (!ALLOWED_POD_MEDIA_TYPES.has(manifestContentType)) {
    throw new Error("POD media manifest Content-Type is not allowed.");
  }
  const rawContentType = header(response.headers, "Content-Type");
  if (!rawContentType) {
    throw new Error("POD media response omitted Content-Type.");
  }
  const contentType = rawContentType.split(";", 1)[0].trim().toLowerCase();
  if (!ALLOWED_POD_MEDIA_TYPES.has(contentType)) {
    throw new Error("POD media response Content-Type is not allowed.");
  }
  if (contentType !== manifestContentType) {
    throw new Error("POD media Content-Type does not match manifest.");
  }
  return {
    body: new Uint8Array(await response.arrayBuffer()),
    contentType,
    cacheControl: "private, no-store",
    referrerPolicy: "no-referrer",
    contentTypeOptions: "nosniff",
    headers: {
      "Content-Type": contentType,
      "Cache-Control": "private, no-store",
      "Referrer-Policy": "no-referrer",
      "X-Content-Type-Options": "nosniff",
    },
  };
}
py
def fetch_pod_manifest(tracking_number: str, *, token: str, client, base_url: str = API_BASE_URL):
    if not token:
        raise ValueError("A pod:read bearer token is required.")
    response = client.get(
        _url(base_url, f"/client/shipments/{quote(tracking_number, safe='')}/pod/"),
        headers={"Accept": "application/json", "Authorization": f"Bearer {token}"},
    )
    if response.status_code == 200:
        return response.json()
    if response.status_code == 403:
        raise PermissionError("POD access was forbidden.")
    if response.status_code == 404:
        raise LookupError("POD manifest was not found.")
    raise RuntimeError(f"POD manifest request failed with HTTP {response.status_code}.")


def _manifest_media(manifest):
    media = []
    for batch in manifest.get("delivery_batches", []):
        media.extend(batch.get("photos", []))
        if batch.get("signature"):
            media.append(batch["signature"])
    return [item for item in media if isinstance(item.get("url"), str)]


def media_key_from_manifest_url(url: str, *, base_url: str = API_BASE_URL) -> str:
    parsed = urlparse(urljoin(base_url, url))
    return parsed.path + (f"?{parsed.query}" if parsed.query else "")


def fetch_pod_media(
    *,
    tracking_number: str,
    manifest,
    media_key: str,
    token: str,
    client,
    base_url: str = API_BASE_URL,
):
    if not token:
        raise ValueError("A pod:read bearer token is required.")
    if not isinstance(media_key, str) or not media_key:
        raise ValueError("mediaKey must come from the POD manifest.")
    media = next(
        (
            item
            for item in _manifest_media(manifest)
            if media_key_from_manifest_url(item["url"], base_url=base_url)
            == media_key
        ),
        None,
    )
    if media is None:
        raise ValueError("mediaKey is not present in the current POD manifest.")
    base = urlparse(base_url)
    selected = urlparse(urljoin(base_url, media_key))
    expected_prefix = (
        f"{base.path.rstrip('/')}/client/shipments/"
        f"{quote(tracking_number, safe='')}/pod/"
    )
    if (
        selected.scheme != base.scheme
        or selected.netloc != base.netloc
        or not selected.path.startswith(expected_prefix)
    ):
        raise ValueError("mediaKey is outside the current shipment POD prefix.")
    response = client.get(selected.geturl(), headers={"Authorization": f"Bearer {token}"})
    if response.status_code == 403:
        raise PermissionError("POD media access was forbidden.")
    if response.status_code == 404:
        raise LookupError("POD media was not found.")
    if response.status_code != 200:
        raise RuntimeError(f"POD media request failed with HTTP {response.status_code}.")
    manifest_content_type = media.get("content_type")
    if manifest_content_type not in ALLOWED_POD_MEDIA_TYPES:
        raise ValueError("POD media manifest Content-Type is not allowed.")
    raw_content_type = _header(response, "Content-Type")
    if raw_content_type is None:
        raise ValueError("POD media response omitted Content-Type.")
    content_type = raw_content_type.split(";", 1)[0].strip().lower()
    if content_type not in ALLOWED_POD_MEDIA_TYPES:
        raise ValueError("POD media response Content-Type is not allowed.")
    if content_type != manifest_content_type:
        raise ValueError("POD media Content-Type does not match manifest.")
    return {
        "body": response.content,
        "content_type": content_type,
        "cache_control": "private, no-store",
        "referrer_policy": "no-referrer",
        "content_type_options": "nosniff",
        "headers": {
            "Content-Type": content_type,
            "Cache-Control": "private, no-store",
            "Referrer-Policy": "no-referrer",
            "X-Content-Type-Options": "nosniff",
        },
    }

Return the upstream bytes only from your authenticated route. The helper accepts only manifest media types image/png, image/jpeg, and image/svg+xml; it requires an upstream 200 Content-Type, normalizes away parameters, and rejects missing, HTML, disallowed, or manifest-mismatched types before returning bytes. The helper result supplies the normalized Content-Type and the exact headers for your surrounding backend response: Cache-Control: private, no-store, Referrer-Policy: no-referrer, and X-Content-Type-Options: nosniff.

Handle access errors ​

Missing, malformed, invalid, revoked, or wrong-scope tokens return 403. Unknown or hidden shipments, mixed-client batches, missing media, and unavailable evidence return an indistinguishable detail-only 404. Do not use these responses to reveal whether another customer's shipment or evidence exists.

Verify the flow ​

  • [ ] The same server-only pod:read token fetches the manifest and selected media.
  • [ ] pending, partial, complete, empty arrays, and nullable fields are handled without invented evidence.
  • [ ] Batches remain separate and newest-first; only completed eligible batches are returned, and package mapping uses sequence_number.
  • [ ] Valid captured location uses persisted Decimal strings; unavailable, incomplete, invalid, out-of-range, and non-captured locations use all-null coordinates.
  • [ ] If using the browser proxy example: it accepts only a manifest-derived application-owned key and validates the current POD prefix.
  • [ ] Photo and signature bytes retain an allowed Content-Type that matches the manifest, plus all three required security headers.
  • [ ] No token or raw signature strokes reach a browser, URL, HTML, log, mobile surface, or CDN.

Return to Webhooks to trigger backend refreshes when POD becomes complete.

M Express server-to-server integration guide