Prometheus

FOXPOST Parcel Tracking

v1Published

Live FOXPOST parcel tracking for a given parcel ID: current status, sender, delivery point, and the full event timeline as JSON.

Author's sample data
events
sendervinted user
parcelIdCLFOX170836973253413
orderNumbernull
trackingUrlhttps://foxpost.hu/csomagkovetes?code=CLFOX170836973253413
currentStatusÁtvéve
cashOnDeliveryVan utánvét
deliveryMethodFOXPOST A-BOX Érd Spar Tárnoki út
paymentOptions
deliveryPointUrlhttps://foxpost.hu/~/hu654
currentStatusDate2024-02-21T08:48:00
Publisher
0 subscribers
mogery@mogery
Every day at 9:00 AM0 runs in 14d · published 6h ago
Parameters
--parcel-idstringrequiredThe FOXPOST parcel identifier to track (starts with CLFOX or Z, or a numeric ID for home delivery). e.g. "CLFOX170836973253413"
Versions
managed by author
v1builtapprovedcurrent6h ago
Schedulesdeploy to enable

Run this collector on a cadence — daily, hourly, your call.

API endpointdeploy to unlock

POST to run it on demand and get fresh data in the response.

Destinationsdeploy to route

Deliver every run to S3, Postgres, a Google Sheet, or a webhook — automatically.

Activitydeploy to track

0 subscriber runs in the last 14 days.

How this script collects data
import Firecrawl from "@mendable/firecrawl-js";
import * as cheerio from "cheerio";
import { parseArgs } from "node:util";

const apiKey = process.env.FIRECRAWL_API_KEY;
if (!apiKey) {
  console.error("FIRECRAWL_API_KEY is not set");
  process.exit(1);
}
const firecrawl = new Firecrawl({ apiKey });

const { values: flags } = parseArgs({
  strict: true,
  options: {
    "parcel-id": { type: "string" },
  },
});
if (!flags["parcel-id"]) {
  console.error("--parcel-id is required (e.g. --parcel-id=CLFOX170836973253413)");
  process.exit(1);
}
const parcelId = flags["parcel-id"].trim();
if (!/^[A-Za-z0-9-]{4,40}$/.test(parcelId)) {
  throw new Error(
    "OUT_OF_SCOPE: invalid parcel ID format — expected an alphanumeric FOXPOST parcel ID (e.g. CLFOX..., Z... or a numeric ID)"
  );
}

// Hungarian labels on the tracking page mapped to English field names
const FIELD_MAP: Record<string, string> = {
  "Csomagszám": "parcelId",
  "Feladó": "sender",
  "Utánvétel": "cashOnDelivery",
  "Kézbesítés módja": "deliveryMethod",
  "Megrendelés száma": "orderNumber",
};

function toIsoDate(raw: string): string | null {
  // page format: "2024.02.21 08:48"
  const m = raw.match(/^(\d{4})\.(\d{2})\.(\d{2})\s+(\d{2}):(\d{2})$/);
  if (!m) return null;
  return `${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:00`;
}

async function main() {
  const url = `https://foxpost.hu/csomagkovetes?code=${encodeURIComponent(parcelId)}`;
  console.error(`Fetching ${url}`);
  const doc = await firecrawl.scrape(url, {
    formats: ["html"],
    integration: "prometheus",
  });
  const html = (doc as { html?: string }).html;
  if (!html) {
    throw new Error("scrape returned no HTML for the FOXPOST tracking page");
  }
  const $ = cheerio.load(html);

  const warning = $(".warning-box").text().trim();
  if (/nem található/i.test(warning)) {
    throw new Error(`OUT_OF_SCOPE: parcel ID "${parcelId}" was not found in the FOXPOST system`);
  }

  if ($("#parcel-number").length === 0) {
    throw new Error("expected #parcel-number element not found on the tracking page");
  }

  // Parcel details: dt/dd label-value pairs in the packet info block
  const info: Record<string, string | null> = {
    parcelId: null,
    sender: null,
    cashOnDelivery: null,
    deliveryMethod: null,
    orderNumber: null,
  };
  $(".timeline-packet-info dt").each((_, el) => {
    const label = $(el).text().trim();
    const key = FIELD_MAP[label];
    if (!key) return;
    const value = $(el).next("dd").text().trim();
    info[key] = value && value !== "-" ? value : null;
  });

  // Link to the delivery point (locker) page, if present; Firecrawl may
  // return the href as relative (/~/xx) or already absolutized
  const deliveryPointPath = $(".timeline-packet-info a[href*='/~/']").attr("href");
  const deliveryPointUrl = deliveryPointPath
    ? deliveryPointPath.startsWith("http")
      ? deliveryPointPath
      : `https://foxpost.hu${deliveryPointPath}`
    : null;

  // Accepted payment methods (icon titles)
  const paymentOptions: string[] = [];
  $(".tracking-packet-info__payment-icons img").each((_, el) => {
    const title = ($(el).attr("title") || "").trim();
    if (title) paymentOptions.push(title);
  });

  // Tracking events, newest first as rendered on the page
  const events: { date: string | null; dateRaw: string; status: string; description: string }[] = [];
  $(".parcel-status-items__list-item").each((_, el) => {
    const dateRaw = $(el).find(".parcel-status-items__list-item-date").text().trim();
    const status = $(el).find(".parcel-status-items__list-item-title").text().trim();
    const description = $(el)
      .find(".parcel-status-items__list-item-description")
      .first()
      .text()
      .replace(/\s+/g, " ")
      .trim();
    if (status) {
      events.push({ date: toIsoDate(dateRaw), dateRaw, status, description });
    }
  });
  if (events.length === 0) {
    throw new Error("no tracking events (.parcel-status-items__list-item) found on the tracking page");
  }

  const latest = events[0];
  const out = {
    parcelId: info.parcelId ?? parcelId,
    currentStatus: latest.status,
    currentStatusDate: latest.date ?? latest.dateRaw,
    sender: info.sender,
    cashOnDelivery: info.cashOnDelivery,
    deliveryMethod: info.deliveryMethod,
    deliveryPointUrl,
    orderNumber: info.orderNumber,
    paymentOptions,
    events,
    trackingUrl: url,
  };
  process.stdout.write(JSON.stringify(out));
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});
Build prompt
FOXPOST Parcel Tracking
One person builds it. Everyone keeps it fresh.