Skip to main content
Glama
marcusquinn

Amazon Order History CSV Download MCP

by marcusquinn

get_amazon_orders

Download Amazon order history as CSV files for specified date ranges or years, including order summaries, item details, and shipment tracking data for reporting and analysis.

Instructions

Fetch Amazon order history for a specified date range or year. Returns order summaries including: order ID, date, total amount, status, item count, shipping address (7 lines), payment method, and Subscribe & Save frequency. Optionally includes detailed item data (ASIN, name, price, quantity, seller, condition) and shipment tracking. Use for browsing order history or building reports.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
regionYesAmazon region code. Supported: us, uk, ca, de, fr, es, it, nl, jp, au, mx, in, ae, sa, ie, be
yearNoYear to fetch orders from (e.g., 2024). If omitted, uses current year.
start_dateNoStart date in ISO format (YYYY-MM-DD). Overrides year if provided.
end_dateNoEnd date in ISO format (YYYY-MM-DD). Overrides year if provided.
include_itemsNoExtract item details (ASIN, name, price, quantity, seller, condition) from each order's invoice page. Adds ~2s per order.
include_shipmentsNoExtract shipment info (delivery status, tracking link) from each order's detail page. Adds ~2s per order. Note: tracking link URL is captured but not the carrier tracking number - use fetch_tracking_numbers for that.
fetch_tracking_numbersNoExtract actual carrier tracking numbers (e.g., AZ218181365JE) by visiting each shipment's 'Track package' page. Adds ~2s per shipment. Only works when include_shipments is true.
max_ordersNoMaximum number of orders to fetch. Use to limit results for large accounts or avoid timeouts.

Implementation Reference

  • Core handler function that orchestrates the entire process of fetching Amazon orders: navigates to order history pages, handles pagination, extracts order summaries from list view, and conditionally visits individual order detail/invoice pages to extract items, shipments, tracking numbers, and transactions using Playwright selectors.
    export async function fetchOrders(
      page: Page,
      plugin: AmazonPlugin,
      options: FetchOrdersOptions,
    ): Promise<FetchOrdersResult> {
      const {
        region,
        year,
        startDate,
        endDate,
        includeItems = false,
        includeShipments = false,
        includeTransactions = false,
        fetchTrackingNumbers = false,
        useInvoice = true, // Default to invoice extraction (faster)
        maxOrders,
        orderId,
        onProgress,
      } = options;
    
      const result: FetchOrdersResult = {
        orders: [],
        items: [],
        shipments: [],
        transactions: [],
        totalFound: 0,
        errors: [],
      };
    
      const regionConfig = getRegionByCode(region);
      if (!regionConfig) {
        result.errors.push(
          `Unknown region: ${region}. Valid regions: us, uk, ca, de, fr, es, it, nl, jp, au, mx, in, ae, sa, ie, be`,
        );
        return result;
      }
      const domain = regionConfig.domain;
      const currency = regionConfig.currency || "USD";
      const currencySymbol =
        currency === "GBP" ? "£" : currency === "EUR" ? "€" : "$";
    
      try {
        // If specific orderId requested, skip order list and go directly to invoice/detail
        if (orderId) {
          console.error(
            `[fetch-orders] Fetching single order: ${orderId} (region: ${region})`,
          );
          onProgress?.(`Fetching order ${orderId}...`, 0, 1);
    
          // Create header for this order
          const header: OrderHeader = {
            id: orderId,
            orderId,
            date: null,
            total: { amount: 0, currency, currencySymbol, formatted: "" },
            detailUrl: `https://www.${domain}/gp/your-account/order-details?orderID=${orderId}`,
            platform: "amazon",
            region,
          };
    
          const enrichedOrder: EnrichedOrder = { ...header };
    
          // Go to invoice page and extract items directly (inline, with timeouts)
          const invoiceUrl = `https://www.${domain}/gp/css/summary/print.html?orderID=${orderId}`;
          console.error(`[fetch-orders] Navigating to invoice: ${invoiceUrl}`);
    
          await page.goto(invoiceUrl, {
            waitUntil: "domcontentloaded",
            timeout: 15000,
          });
          const currentUrl = page.url();
          console.error(`[fetch-orders] Current URL: ${currentUrl}`);
    
          await page
            .waitForSelector('[data-component="purchasedItems"], table, .a-box', {
              timeout: 3000,
            })
            .catch(() => {});
    
          // Check for error banners (e.g., "We're unable to load your order details")
          const errorBanner = await page
            .locator(
              '[data-component="errorbanner"], .a-alert-error, .a-alert-info',
            )
            .first();
          const errorBannerCount = await errorBanner.count().catch(() => 0);
          if (errorBannerCount > 0) {
            const errorText = await errorBanner
              .textContent({ timeout: 500 })
              .catch(() => "");
            if (
              errorText?.includes("unable to load") ||
              errorText?.includes("problem loading")
            ) {
              console.error(
                `[fetch-orders] Error detected: ${errorText.slice(0, 200)}`,
              );
              result.errors.push(
                `Order page error: ${errorText.slice(0, 200).trim()}`,
              );
            }
          }
    
          // Try to extract items - first try data-component, then fall back to extractFromInvoice
          const itemContainers = await page
            .locator('[data-component="purchasedItems"]')
            .all();
          console.error(
            `[fetch-orders] Found ${itemContainers.length} purchasedItems containers on invoice page`,
          );
    
          // Invoice pages often don't have data-component, try the full invoice extractor
          if (itemContainers.length === 0) {
            console.error(
              `[fetch-orders] No data-component items found, using extractFromInvoice`,
            );
            const invoiceData = await extractFromInvoice(page, header);
    
            // Copy over invoice data (amounts, recipient, payments) regardless of items
            console.error(
              `[fetch-orders] Invoice data: subtotal=${invoiceData.subtotal?.formatted}, total=${invoiceData.total?.formatted}, vat=${invoiceData.vat?.formatted}, shipping=${invoiceData.shipping?.formatted}`,
            );
            if (invoiceData.subtotal) enrichedOrder.subtotal = invoiceData.subtotal;
            if (invoiceData.total) enrichedOrder.grandTotal = invoiceData.total;
            if (invoiceData.shipping) enrichedOrder.shipping = invoiceData.shipping;
            if (invoiceData.tax) enrichedOrder.tax = invoiceData.tax;
            if (invoiceData.vat) enrichedOrder.vat = invoiceData.vat;
            if (invoiceData.gift) enrichedOrder.promotion = invoiceData.gift;
            if (invoiceData.recipientName) {
              enrichedOrder.recipient = invoiceData.recipientName;
              if (invoiceData.shippingAddress) {
                // Prepend recipient name as line1 if address doesn't start with it
                const addressWithName = [
                  invoiceData.recipientName,
                  ...invoiceData.shippingAddress,
                ];
                enrichedOrder.shippingAddress =
                  parseInvoiceAddressLines(addressWithName);
              }
            }
            if (invoiceData.payments && invoiceData.payments.length > 0) {
              enrichedOrder.payments = invoiceData.payments;
              // Also set paymentMethod from first payment
              const firstPayment = invoiceData.payments[0];
              enrichedOrder.paymentMethod = {
                type: firstPayment.method,
                lastFour: firstPayment.lastFour,
              };
            }
    
            // Convert invoice items to full Item type if found
            if (invoiceData.items && invoiceData.items.length > 0) {
              // Create enriched header with all order data for items
              const enrichedHeader: OrderHeader = {
                ...header,
                recipient:
                  typeof enrichedOrder.recipient === "string"
                    ? enrichedOrder.recipient
                    : undefined,
                subtotal: enrichedOrder.subtotal,
                shipping: enrichedOrder.shipping,
                tax: enrichedOrder.tax,
                vat: enrichedOrder.vat,
                promotion: enrichedOrder.promotion,
                grandTotal: enrichedOrder.grandTotal,
                shippingAddress: enrichedOrder.shippingAddress,
                paymentMethod: enrichedOrder.paymentMethod,
              };
    
              const items = invoiceData.items.map((ii) => ({
                id: ii.asin || ii.name.slice(0, 50),
                asin: ii.asin,
                name: ii.name,
                quantity: ii.quantity,
                unitPrice: ii.unitPrice,
                totalPrice: {
                  ...ii.unitPrice,
                  amount: ii.unitPrice.amount * ii.quantity,
                  formatted: `${currencySymbol}${(ii.unitPrice.amount * ii.quantity).toFixed(2)}`,
                },
                url: ii.asin ? `https://www.${domain}/dp/${ii.asin}` : "",
                orderHeader: enrichedHeader,
                condition: ii.condition,
                seller: ii.seller ? { name: ii.seller } : undefined,
                subscriptionFrequency: ii.subscriptionFrequency,
                platformData: { source: "invoice" },
              }));
    
              console.error(
                `[fetch-orders] extractFromInvoice found ${items.length} items`,
              );
              enrichedOrder.items = items;
              result.items = items;
            }
          }
    
          // Only use inline extraction if we found data-component containers (detail page)
          // Otherwise extractFromInvoice already handled it above
          if (itemContainers.length > 0) {
            // Create enriched header with all order data for items
            const enrichedHeader: OrderHeader = {
              ...header,
              recipient:
                typeof enrichedOrder.recipient === "string"
                  ? enrichedOrder.recipient
                  : undefined,
              subtotal: enrichedOrder.subtotal,
              shipping: enrichedOrder.shipping,
              tax: enrichedOrder.tax,
              vat: enrichedOrder.vat,
              promotion: enrichedOrder.promotion,
              grandTotal: enrichedOrder.grandTotal,
              shippingAddress: enrichedOrder.shippingAddress,
              paymentMethod: enrichedOrder.paymentMethod,
            };
    
            const extractedItems: Item[] = [];
            for (const container of itemContainers) {
              try {
                // Title + ASIN
                const titleLink = container
                  .locator('[data-component="itemTitle"] a')
                  .first();
                const titleCount = await titleLink.count().catch(() => 0);
                if (titleCount === 0) continue;
    
                const name = await titleLink
                  .textContent({ timeout: 500 })
                  .catch(() => "");
                if (!name?.trim()) continue;
    
                const href = await titleLink
                  .getAttribute("href", { timeout: 500 })
                  .catch(() => "");
                const asinMatch = href?.match(/\/dp\/([A-Z0-9]+)/i);
                const asin = asinMatch ? asinMatch[1] : undefined;
    
                // Price
                const priceEl = container
                  .locator('[data-component="unitPrice"] .a-offscreen')
                  .first();
                const priceText = await priceEl
                  .textContent({ timeout: 500 })
                  .catch(() => "");
                const priceMatch = priceText?.match(/[£$€]?([\d,.]+)/);
                const priceAmount = priceMatch
                  ? parseFloat(priceMatch[1].replace(",", ""))
                  : 0;
    
                // Quantity (check badge first, then quantity component)
                let quantity = 1;
                const qtyBadge = container
                  .locator(".od-item-view-qty span")
                  .first();
                const qtyBadgeCount = await qtyBadge.count().catch(() => 0);
                if (qtyBadgeCount > 0) {
                  const qtyText = await qtyBadge
                    .textContent({ timeout: 500 })
                    .catch(() => "");
                  const qtyMatch = qtyText?.match(/(\d+)/);
                  if (qtyMatch) quantity = parseInt(qtyMatch[1], 10);
                }
    
                // Seller
                let seller: string | undefined;
                const sellerEl = container
                  .locator('[data-component="orderedMerchant"]')
                  .first();
                const sellerCount = await sellerEl.count().catch(() => 0);
                if (sellerCount > 0) {
                  const sellerText = await sellerEl
                    .textContent({ timeout: 500 })
                    .catch(() => "");
                  const sellerMatch = sellerText?.match(/Sold by:\s*(.+)/i);
                  if (sellerMatch) seller = sellerMatch[1].trim();
                }
    
                // Condition
                let condition: string | undefined;
                const condEl = container
                  .locator('[data-component="itemCondition"]')
                  .first();
                const condCount = await condEl.count().catch(() => 0);
                if (condCount > 0) {
                  const condText = await condEl
                    .textContent({ timeout: 500 })
                    .catch(() => "");
                  const condMatch = condText?.match(/Condition:\s*(.+)/i);
                  if (condMatch) condition = condMatch[1].trim();
                }
    
                // Subscription frequency
                let subscriptionFrequency: string | undefined;
                const freqEl = container
                  .locator('[data-component="deliveryFrequency"]')
                  .first();
                const freqCount = await freqEl.count().catch(() => 0);
                if (freqCount > 0) {
                  const freqText = await freqEl
                    .textContent({ timeout: 500 })
                    .catch(() => "");
                  const freqMatch = freqText?.match(/Auto-delivered:\s*(.+)/i);
                  if (freqMatch) subscriptionFrequency = freqMatch[1].trim();
                }
    
                extractedItems.push({
                  id: asin || name.trim().slice(0, 50),
                  asin,
                  name: name.trim(),
                  quantity,
                  unitPrice: {
                    amount: priceAmount,
                    currency,
                    currencySymbol,
                    formatted: `${currencySymbol}${priceAmount.toFixed(2)}`,
                  },
                  totalPrice: {
                    amount: priceAmount * quantity,
                    currency,
                    currencySymbol,
                    formatted: `${currencySymbol}${(priceAmount * quantity).toFixed(2)}`,
                  },
                  url: asin ? `https://www.${domain}/dp/${asin}` : "",
                  orderHeader: enrichedHeader,
                  condition,
                  seller: seller ? { name: seller } : undefined,
                  subscriptionFrequency,
                  platformData: { source: "invoice" },
                });
              } catch {
                continue;
              }
            }
    
            console.error(
              `[fetch-orders] Extracted ${extractedItems.length} items from data-component`,
            );
    
            // Store items
            if (extractedItems.length > 0) {
              enrichedOrder.items = extractedItems;
              result.items = extractedItems;
            }
          }
    
          // If no items found from invoice, try the detail page
          if (result.items.length === 0 && result.errors.length === 0) {
            console.error(
              `[fetch-orders] No items from invoice, trying detail page extraction`,
            );
            await page.goto(header.detailUrl, {
              waitUntil: "domcontentloaded",
              timeout: 15000,
            });
            await page
              .waitForSelector('[data-component="purchasedItems"], .a-box', {
                timeout: 2000,
              })
              .catch(() => {});
    
            // Check for error banners on detail page
            const detailErrorBanner = await page
              .locator(
                '[data-component="errorbanner"], .a-alert-error, .a-alert-info',
              )
              .first();
            const detailErrorCount = await detailErrorBanner.count().catch(() => 0);
            if (detailErrorCount > 0) {
              const errorText = await detailErrorBanner
                .textContent({ timeout: 500 })
                .catch(() => "");
              if (
                errorText?.includes("unable to load") ||
                errorText?.includes("problem loading")
              ) {
                console.error(
                  `[fetch-orders] Detail page error: ${errorText.slice(0, 200)}`,
                );
                result.errors.push(
                  `Order detail error: ${errorText.slice(0, 200).trim()}`,
                );
              }
            }
    
            if (result.errors.length === 0) {
              const items = await plugin.extractItems(page, header).catch(() => []);
              if (items.length > 0) {
                console.error(
                  `[fetch-orders] Found ${items.length} items from detail page`,
                );
                enrichedOrder.items = items;
                result.items = items;
              }
            }
          }
    
          // Get shipments from detail page if requested
          if (includeShipments) {
            console.error(`[fetch-orders] Fetching shipments from detail page`);
            // Only navigate if not already there
            if (!page.url().includes("order-details")) {
              await page.goto(header.detailUrl, {
                waitUntil: "domcontentloaded",
                timeout: 15000,
              });
              await page
                .waitForSelector('[data-component="shipments"], .a-box', {
                  timeout: 2000,
                })
                .catch(() => {});
            }
    
            const shipments = await plugin
              .extractShipments(page, header, fetchTrackingNumbers)
              .catch(() => []);
            enrichedOrder.shipments = shipments;
            result.shipments = shipments;
            console.error(`[fetch-orders] Found ${shipments.length} shipments`);
          }
    
          // Get transactions if requested
          if (includeTransactions) {
            const transactions = await plugin
              .extractTransactions(page, header)
              .catch(() => []);
            result.transactions = transactions;
            console.error(
              `[fetch-orders] Found ${transactions.length} transactions`,
            );
          }
    
          result.orders = [enrichedOrder];
          result.totalFound = 1;
    
          return result;
        }
    
        // Build order list URL (for multi-order fetch)
        const listUrl = plugin.getOrderListUrl(region, {
          year,
          startDate: startDate ? new Date(startDate) : undefined,
          endDate: endDate ? new Date(endDate) : undefined,
        });
    
        // Navigate to order list
        console.error(`[fetch-orders] Navigating to: ${listUrl}`);
        onProgress?.("Navigating to order history...", 0, 0);
        await page.goto(listUrl, { waitUntil: "domcontentloaded", timeout: 60000 });
        // Wait for order cards to appear (more reliable than fixed timeout)
        await page
          .waitForSelector('.order-card, [class*="order-card"], .a-box-group', {
            timeout: 3000,
          })
          .catch(() => {});
        console.error(`[fetch-orders] Page loaded, URL: ${page.url()}`);
    
        // Check authentication
        console.error(`[fetch-orders] Checking auth...`);
        const authStatus = await plugin.checkAuthStatus(page, region);
        console.error(`[fetch-orders] Auth result: ${JSON.stringify(authStatus)}`);
        if (!authStatus.authenticated) {
          result.errors.push(`Not authenticated: ${authStatus.message}`);
          return result;
        }
    
        // Extract orders from all pages
        let pageNum = 1;
        let hasMore = true;
    
        while (hasMore) {
          console.error(`[fetch-orders] Extracting page ${pageNum}...`);
          onProgress?.(
            `Extracting orders from page ${pageNum}...`,
            result.orders.length,
            0,
          );
    
          // Extract order headers from current page
          const pageHeaders = await extractOrderHeaders(page, region);
          console.error(
            `[fetch-orders] Found ${pageHeaders.length} orders on page ${pageNum}`,
          );
          // Cast to the enriched type for result storage
          result.orders.push(...(pageHeaders as EnrichedOrder[]));
          onProgress?.(
            `Found ${result.orders.length} orders (page ${pageNum})...`,
            result.orders.length,
            0,
          );
    
          // Check if we've hit the max
          if (maxOrders && result.orders.length >= maxOrders) {
            result.orders = result.orders.slice(0, maxOrders);
            break;
          }
    
          // Check for next page
          hasMore = await hasNextPage(page);
          console.error(`[fetch-orders] Has next page: ${hasMore}`);
          if (hasMore && result.orders.length < (maxOrders || 1000)) {
            const navigated = await goToNextPage(page);
            console.error(`[fetch-orders] Navigated to next: ${navigated}`);
            if (!navigated) break;
            pageNum++;
          } else {
            hasMore = false;
          }
        }
    
        result.totalFound = result.orders.length;
        const extractionMode = useInvoice ? "invoice" : "detail";
        onProgress?.(
          `Found ${result.totalFound} orders, starting ${extractionMode} extraction...`,
          0,
          result.totalFound,
        );
    
        // If detailed extraction requested, visit each order
        if (includeItems || includeShipments || includeTransactions) {
          const startTime = Date.now();
          let processedCount = 0;
    
          for (let i = 0; i < result.orders.length; i++) {
            const order = result.orders[i];
    
            // Calculate ETA based on average time per order
            const elapsed = Date.now() - startTime;
            const avgTimePerOrder =
              processedCount > 0
                ? elapsed / processedCount
                : useInvoice
                  ? 1500
                  : 3000;
            const remaining = result.orders.length - i;
            const etaSeconds = Math.round((avgTimePerOrder * remaining) / 1000);
            const etaStr =
              etaSeconds > 60
                ? `~${Math.round(etaSeconds / 60)}m ${etaSeconds % 60}s`
                : `~${etaSeconds}s`;
    
            console.error(
              `[fetch-orders] Processing order ${i + 1}/${result.orders.length}: ${order.id} (${extractionMode} mode)`,
            );
            onProgress?.(
              `Order ${i + 1}/${result.orders.length} (${order.id}) - ETA: ${etaStr}`,
              i + 1,
              result.orders.length,
            );
    
            // Skip cancelled orders - they have no useful detail to extract
            const orderStatus = order.status?.label?.toLowerCase() || "";
            if (orderStatus === "cancelled") {
              console.error(`[fetch-orders] Skipping cancelled order ${order.id}`);
              order.items = [];
              processedCount++;
              continue;
            }
    
            // Create header for extraction functions
            let recipientStr: string | undefined;
            if (typeof order.recipient === "string") {
              recipientStr = order.recipient;
            } else if (
              order.recipient &&
              typeof order.recipient === "object" &&
              "name" in order.recipient
            ) {
              recipientStr = (order.recipient as { name: string }).name;
            }
    
            const header: OrderHeader = {
              id: order.id,
              orderId: order.orderId,
              date: order.date,
              total: order.total,
              detailUrl: order.detailUrl,
              recipient: recipientStr,
              platform: order.platform,
              region: order.region,
            };
    
            try {
              if (useInvoice) {
                // Invoice-based extraction (faster, cleaner HTML)
                console.error(
                  `[fetch-orders] Using invoice extraction for ${order.id}`,
                );
                onProgress?.(
                  `Order ${i + 1}/${result.orders.length} - Loading invoice...`,
                  i,
                  result.orders.length,
                );
                const invoiceData = await extractFromInvoice(page, header);
    
                // Merge all invoice data into order (amounts, recipient, payments)
                if (invoiceData.subtotal) order.subtotal = invoiceData.subtotal;
                if (invoiceData.shipping) order.shipping = invoiceData.shipping;
                if (invoiceData.tax) order.tax = invoiceData.tax;
                if (invoiceData.vat) order.vat = invoiceData.vat;
                if (invoiceData.total) order.grandTotal = invoiceData.total;
                if (invoiceData.gift) order.promotion = invoiceData.gift;
                if (invoiceData.payments && invoiceData.payments.length > 0) {
                  order.payments = invoiceData.payments;
                  // Also set paymentMethod from first payment
                  const firstPayment = invoiceData.payments[0];
                  order.paymentMethod = {
                    type: firstPayment.method,
                    lastFour: firstPayment.lastFour,
                  };
                }
                if (invoiceData.recipientName) {
                  order.recipient = invoiceData.recipientName;
                  if (invoiceData.shippingAddress) {
                    // Prepend recipient name as line1 if address doesn't start with it
                    const addressWithName = [
                      invoiceData.recipientName,
                      ...invoiceData.shippingAddress,
                    ];
                    order.shippingAddress =
                      parseInvoiceAddressLines(addressWithName);
                  }
                }
    
                // Extract items from invoice if requested
                if (includeItems) {
                  // Create enriched header with all order data for items
                  // This includes shippingAddress, status, etc. that were merged above
                  const enrichedHeader: OrderHeader = {
                    id: order.id,
                    orderId: order.orderId,
                    date: order.date,
                    total: order.total,
                    detailUrl: order.detailUrl,
                    recipient: recipientStr,
                    status: order.status,
                    platform: order.platform,
                    region: order.region,
                    subtotal: order.subtotal,
                    shipping: order.shipping,
                    tax: order.tax,
                    vat: order.vat,
                    promotion: order.promotion,
                    grandTotal: order.grandTotal,
                    shippingAddress: order.shippingAddress,
                    paymentMethod: order.paymentMethod,
                    subscribeAndSave: order.subscribeAndSave,
                  };
    
                  // Check if invoice has items and matches expected count from order list
                  const expectedItemCount = order.itemCount || 0;
                  const invoiceItemCount = invoiceData.items?.length || 0;
    
                  if (
                    invoiceData.items &&
                    invoiceItemCount > 0 &&
                    (expectedItemCount === 0 ||
                      invoiceItemCount >= expectedItemCount)
                  ) {
                    // Invoice has items and count looks correct - use invoice data
                    const items = invoiceData.items.map((ii) => ({
                      id: ii.asin || ii.name.slice(0, 50),
                      asin: ii.asin,
                      name: ii.name,
                      quantity: ii.quantity,
                      unitPrice: ii.unitPrice,
                      totalPrice: {
                        ...ii.unitPrice,
                        amount: ii.unitPrice.amount * ii.quantity,
                      },
                      url: ii.asin
                        ? `https://www.${regionConfig.domain}/dp/${ii.asin}`
                        : "",
                      orderHeader: enrichedHeader,
                      condition: ii.condition,
                      seller: ii.seller ? { name: ii.seller } : undefined,
                      subscriptionFrequency: ii.subscriptionFrequency,
                      platformData: { source: "invoice" },
                    }));
                    console.error(
                      `[fetch-orders] Found ${items.length} items from invoice (expected ${expectedItemCount})`,
                    );
                    result.items.push(...items);
                    order.items = items;
                  } else {
                    // Fallback to detail page if:
                    // - Invoice extraction found no items, OR
                    // - Invoice item count doesn't match expected count from order list
                    console.error(
                      `[fetch-orders] Invoice has ${invoiceItemCount} items but expected ${expectedItemCount}, falling back to detail page`,
                    );
                    await page.goto(order.detailUrl, {
                      waitUntil: "domcontentloaded",
                      timeout: 15000,
                    });
                    await page
                      .waitForSelector('.a-box, [data-component="orderDetails"]', {
                        timeout: 1000,
                      })
                      .catch(() => {});
                    const items = await plugin
                      .extractItems(page, enrichedHeader)
                      .catch(() => []);
                    console.error(
                      `[fetch-orders] Found ${items.length} items from detail page`,
                    );
                    result.items.push(...items);
                    order.items = items;
                  }
                }
    
                // Shipments need detail page (not on invoice)
                if (includeShipments) {
                  console.error(
                    `[fetch-orders] Fetching shipments from detail page`,
                  );
                  onProgress?.(
                    `Order ${i + 1}/${result.orders.length} - Fetching shipments...`,
                    i,
                    result.orders.length,
                  );
                  await page.goto(order.detailUrl, {
                    waitUntil: "domcontentloaded",
                    timeout: 15000,
                  });
                  await page
                    .waitForSelector(
                      '[data-component="shipments"], .shipment-is-delivered, .a-box',
                      { timeout: 1000 },
                    )
                    .catch(() => {});
                  const shipments = await plugin
                    .extractShipments(page, header, fetchTrackingNumbers)
                    .catch(() => []);
                  result.shipments.push(...shipments);
                  order.shipments = shipments;
                }
    
                // Transactions from detail page
                if (includeTransactions) {
                  if (!includeShipments) {
                    await page.goto(order.detailUrl, {
                      waitUntil: "domcontentloaded",
                      timeout: 15000,
                    });
                  }
                  const transactions = await plugin
                    .extractTransactions(page, header)
                    .catch(() => []);
                  result.transactions.push(...transactions);
                }
    
                processedCount++;
              } else {
                // Detail page extraction (original method)
                console.error(`[fetch-orders] Navigating to: ${order.detailUrl}`);
                onProgress?.(
                  `Order ${i + 1}/${result.orders.length} - Loading details...`,
                  i,
                  result.orders.length,
                );
                await page.goto(order.detailUrl, {
                  waitUntil: "domcontentloaded",
                  timeout: 30000,
                });
                await page
                  .waitForSelector(
                    '#od-subtotals, [data-component="orderDetails"], .order-details, .a-box',
                    { timeout: 1500 },
                  )
                  .catch(() => {});
                console.error(`[fetch-orders] Page loaded for order ${order.id}`);
    
                // Run extractions in parallel for speed
                const extractionPromises: Promise<void>[] = [];
    
                // Order details extraction
                extractionPromises.push(
                  extractOrderDetails(page, region)
                    .then((details) => {
                      Object.assign(order, details);
                      console.error(`[fetch-orders] Order details extracted`);
                    })
                    .catch(() => {}),
                );
    
                // Items extraction
                if (includeItems) {
                  extractionPromises.push(
                    plugin
                      .extractItems(page, header)
                      .then((items) => {
                        console.error(`[fetch-orders] Found ${items.length} items`);
                        result.items.push(...items);
                        order.items = items;
                      })
                      .catch(() => {
                        order.items = [];
                      }),
                  );
                }
    
                // Shipments extraction
                if (includeShipments) {
                  extractionPromises.push(
                    plugin
                      .extractShipments(page, header, fetchTrackingNumbers)
                      .then((shipments) => {
                        result.shipments.push(...shipments);
                        order.shipments = shipments;
                      })
                      .catch(() => {}),
                  );
                }
    
                // Transactions extraction
                if (includeTransactions) {
                  extractionPromises.push(
                    plugin
                      .extractTransactions(page, header)
                      .then((transactions) => {
                        result.transactions.push(...transactions);
                      })
                      .catch(() => {}),
                  );
                }
    
                // Wait for all extractions to complete
                await Promise.all(extractionPromises);
                processedCount++;
              }
            } catch (error) {
              result.errors.push(`Error extracting order ${order.id}: ${error}`);
              processedCount++;
            }
          }
        }
    
        onProgress?.(
          `Complete! ${result.orders.length} orders, ${result.items.length} items`,
          result.orders.length,
          result.orders.length,
        );
        return result;
      } catch (error) {
        result.errors.push(`Fetch error: ${error}`);
        return result;
      }
    }
  • src/index.ts:133-186 (registration)
    Tool registration in the MCP tools array, including name, description, and input schema.
    {
      name: "get_amazon_orders",
      description:
        "Fetch Amazon order history for a specified date range or year. Returns order summaries including: order ID, date, total amount, status, item count, shipping address (7 lines), payment method, and Subscribe & Save frequency. Optionally includes detailed item data (ASIN, name, price, quantity, seller, condition) and shipment tracking. Use for browsing order history or building reports.",
      inputSchema: {
        type: "object",
        properties: {
          region: {
            type: "string",
            description: `Amazon region code. Supported: ${getRegionCodes().join(", ")}`,
            enum: getRegionCodes(),
          },
          year: {
            type: "number",
            description:
              "Year to fetch orders from (e.g., 2024). If omitted, uses current year.",
          },
          start_date: {
            type: "string",
            description:
              "Start date in ISO format (YYYY-MM-DD). Overrides year if provided.",
          },
          end_date: {
            type: "string",
            description:
              "End date in ISO format (YYYY-MM-DD). Overrides year if provided.",
          },
          include_items: {
            type: "boolean",
            description:
              "Extract item details (ASIN, name, price, quantity, seller, condition) from each order's invoice page. Adds ~2s per order.",
            default: false,
          },
          include_shipments: {
            type: "boolean",
            description:
              "Extract shipment info (delivery status, tracking link) from each order's detail page. Adds ~2s per order. Note: tracking link URL is captured but not the carrier tracking number - use fetch_tracking_numbers for that.",
            default: false,
          },
          fetch_tracking_numbers: {
            type: "boolean",
            description:
              "Extract actual carrier tracking numbers (e.g., AZ218181365JE) by visiting each shipment's 'Track package' page. Adds ~2s per shipment. Only works when include_shipments is true.",
            default: false,
          },
          max_orders: {
            type: "number",
            description:
              "Maximum number of orders to fetch. Use to limit results for large accounts or avoid timeouts.",
          },
        },
        required: ["region"],
      },
    },
  • Input schema defining parameters for the get_amazon_orders tool: region (required), year, dates, include flags, max_orders.
    inputSchema: {
      type: "object",
      properties: {
        region: {
          type: "string",
          description: `Amazon region code. Supported: ${getRegionCodes().join(", ")}`,
          enum: getRegionCodes(),
        },
        year: {
          type: "number",
          description:
            "Year to fetch orders from (e.g., 2024). If omitted, uses current year.",
        },
        start_date: {
          type: "string",
          description:
            "Start date in ISO format (YYYY-MM-DD). Overrides year if provided.",
        },
        end_date: {
          type: "string",
          description:
            "End date in ISO format (YYYY-MM-DD). Overrides year if provided.",
        },
        include_items: {
          type: "boolean",
          description:
            "Extract item details (ASIN, name, price, quantity, seller, condition) from each order's invoice page. Adds ~2s per order.",
          default: false,
        },
        include_shipments: {
          type: "boolean",
          description:
            "Extract shipment info (delivery status, tracking link) from each order's detail page. Adds ~2s per order. Note: tracking link URL is captured but not the carrier tracking number - use fetch_tracking_numbers for that.",
          default: false,
        },
        fetch_tracking_numbers: {
          type: "boolean",
          description:
            "Extract actual carrier tracking numbers (e.g., AZ218181365JE) by visiting each shipment's 'Track package' page. Adds ~2s per shipment. Only works when include_shipments is true.",
          default: false,
        },
        max_orders: {
          type: "number",
          description:
            "Maximum number of orders to fetch. Use to limit results for large accounts or avoid timeouts.",
        },
      },
      required: ["region"],
  • src/index.ts:648-750 (registration)
    Switch case dispatcher that handles the tool call, validates region, invokes fetchOrders, and formats the JSON response.
    case "get_amazon_orders": {
      const regionParam = args?.region as string | undefined;
      const regionError = validateRegion(regionParam, args);
      if (regionError) return regionError;
      const region = regionParam!; // Validated above
    
      // Get progress token from request meta
      const progressToken = request.params._meta?.progressToken;
    
      const currentPage = await getPage();
      const result = await fetchOrders(currentPage, amazonPlugin, {
        region,
        year: args?.year as number | undefined,
        startDate: args?.start_date as string | undefined,
        endDate: args?.end_date as string | undefined,
        includeItems: args?.include_items as boolean | undefined,
        includeShipments: args?.include_shipments as boolean | undefined,
        fetchTrackingNumbers: args?.fetch_tracking_numbers as
          | boolean
          | undefined,
        maxOrders: args?.max_orders as number | undefined,
        onProgress: async (message, current, total) => {
          await sendProgress(progressToken, current, total, message);
        },
      });
    
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(
              {
                status: "success",
                // Echo input parameters for debugging
                params: {
                  region,
                  year: args?.year,
                  startDate: args?.start_date,
                  endDate: args?.end_date,
                  includeItems: args?.include_items,
                  includeShipments: args?.include_shipments,
                  fetchTrackingNumbers: args?.fetch_tracking_numbers,
                  maxOrders: args?.max_orders,
                },
                totalOrders: result.totalFound,
                orders: result.orders.map((o) => ({
                  id: o.id,
                  date: o.date?.toISOString(),
                  total: o.total,
                  status: o.status?.label || "Unknown",
                  // Use itemCount from order header (extracted from list page) or fall back to items array length
                  itemCount: o.itemCount ?? o.items?.length ?? 0,
                  shipmentCount: o.shipments?.length || 0,
                  // Enhanced order header data from list page
                  subtotal: o.subtotal,
                  shipping: o.shipping,
                  tax: o.tax,
                  vat: o.vat,
                  promotion: o.promotion,
                  grandTotal: o.grandTotal,
                  // Shipping address
                  shippingAddress: o.shippingAddress,
                  // Payment method from list page
                  paymentMethod: o.paymentMethod,
                  // Recipient (simple name)
                  recipient:
                    typeof o.recipient === "object"
                      ? o.recipient
                      : { name: o.recipient },
                  // Payments from detail/invoice
                  payments: o.payments,
                  // Include items when extracted
                  items: o.items?.map((i) => ({
                    name: i.name,
                    asin: i.asin,
                    quantity: i.quantity,
                    unitPrice: i.unitPrice,
                    condition: i.condition,
                    seller: i.seller?.name,
                    subscriptionFrequency: i.subscriptionFrequency,
                  })),
                  // Include shipments when extracted
                  shipments: o.shipments?.map((s) => ({
                    shipmentId: s.shipmentId,
                    status: s.status,
                    delivered: s.delivered,
                    trackingId: s.trackingId,
                    carrier: s.carrier,
                    trackingLink: s.trackingLink,
                    itemCount: s.items?.length || 0,
                  })),
                })),
                itemCount: result.items.length,
                shipmentCount: result.shipments.length,
                errors: result.errors,
              },
              null,
              2,
            ),
          },
        ],
      };
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/marcusquinn/amazon-order-history-csv-download-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server