Resources/API Documentation/Customer Entry Lookup API

API reference

Customer Entry Lookup API

Display a customer’s earned entries using their logged-in email address or an email search—like the email lookup widget, with your own design.

POSThttps://backend.cleansweeps.app/graphql

Operation: QueryCustomer

Build your own customer entry display

Use this API for the same kind of experience as the email lookup widget, with your own layout, styling, and presentation.

  • For a logged-in customer: pass the customer’s email address from your storefront’s signed-in customer context to the email query. Your integration supplies the email; the query does not automatically detect who is logged in.
  • For an email search: let the visitor enter an email address, then use it in the same query to retrieve the customer’s entries.

Read all the pages you need, group the results by sweepstake, and show entry totals in a custom table, card, or customer dashboard.

Request

Send a JSON body with query and variables. Set Content-Type: application/json. Check both the HTTP response and GraphQL errors.

GraphQL
query QueryCustomer(
  $email: String!,
  $platform: String!,
  $platformId: String!,
  $take: Int,
  $skip: Int
) {
  customer(email: $email) {
    entries(platform: $platform, platformId: $platformId, take: $take, skip: $skip) {
      entriesTotal
      sweepstake { title endDate }
    }
    referrer(platform: $platform, platformId: $platformId) {
      referralCode
      usageCount
    }
  }
}

Variables

QueryCustomer variables
FieldTypeDetails
emailString!Required for the email query. Trim and lowercase before lookup.
platformString!Required. Use Shopify.
platformIdString!Required. Shop GID: gid://shopify/Shop/<SHOP_ID>.
takeIntPage size. This example uses 60.
skipIntOffset. Increase by take while a full page is returned.

Email lookup result

Fields
FieldTypeDetails
entries[].entriesTotalNumberEntry value returned for each record. Aggregate across all pages for your display.
entries[].sweepstake.titleStringSweepstake title; used for grouping in the supplied example.
entries[].sweepstake.endDateStringUnix milliseconds in this endpoint’s documentation.
referrerObject or nullOptional referralCode and usageCount, subject to plan/configuration.

B) Lookup by Shopify Customer ID (platformUserId)

If you prefer Shopify’s customer GID (e.g., server-side/Admin portals):

  • platformUserId: gid://shopify/Customer/<CUSTOMER_ID>
Example
query FetchCustomer(
  $platformUserId: String!,
  $skip: Int!,
  $take: Int!,
  $platform: String!,
  $platformId: String!
) {
  customer(platformUserId: $platformUserId) {
    entries(skip: $skip, take: $take, platform: $platform, platformId: $platformId) {
      id
      entriesTotal
      bonusEntries
      sweepstake { title endDate }
    }
    referrer(platform: $platform, platformId: $platformId) {
      referralCode
      usageCount
    }
  }
}

Example: Email Search (Drop-in JS, Unminified + Commented)

This is a complete, working example that:

  • reads an email from an input,
  • pages through results (take=60),
  • (optionally) filters to sweeps that ended within the last 21 days,
  • groups entriesTotal by sweepstake title, and
  • renders a small 2-column table.

To compute a grand total across all sweepstakes, see the comment where indicated.

Example
<script>
  // --- Mutability / guard flags for safe re-init in dynamic theme DOMs ---
  var lastInitializedTime = 0;
  var isInitializing = false;
  var isUpdatingTable = false;
  var hasCompletedInitialization = false;


  document.addEventListener('DOMContentLoaded', function () {
    /**
     * (Optional helper) If you ever need to check whether referrals are enabled for the org.
     * Not used in this widget flow; kept for parity with upstream code.
     */
    function checkReferralsEnabled(organization) {
      let uiConfig;
      try {
        uiConfig = JSON.parse(organization.uiConfig);
      } catch (error) {
        return false;
      }
      // NOTE: 'plan' is compared as a string in the original code.
      // Keep as-is to mirror behavior.
      if (organization.plan > '2' && uiConfig.referralCodes) {
        return true;
      } else {
        return false;
      }
    }


    /**
     * Wire up the click handler once all DOM nodes are present.
     */
    async function initializeElements() {
      if (isInitializing) return;
      if (hasCompletedInitialization) return;


      isInitializing = true;


      var searchButton = document.getElementById('sweepstakes-lookup-search-btn');
      var emailInput   = document.getElementById('sweepstakes-lookup-email');
      var tableBody    = document.getElementById('sweepstakes-lookup-table-body');


      try {
        if (searchButton && emailInput && tableBody) {
          // Ensure a clean, single listener
          searchButton.removeEventListener('click', handleSearchClick);
          searchButton.addEventListener('click', handleSearchClick);
        }
      } catch (error) {
        displayNoResults();
      } finally {
        isInitializing = false;
        hasCompletedInitialization = true;
      }
    }


    /**
     * Read email and fire the lookup.
     */
    function handleSearchClick() {
      var email = document
        .getElementById('sweepstakes-lookup-email')
        .value
        .trim()
        .toLowerCase();


      fetchCustomerDetails(email);
    }


    // First run
    initializeElements();


    /**
     * Simple debounce helper for noisy mutation events.
     */
    function debounce(func, wait) {
      let timeout;
      return function (...args) {
        const later = () => {
          clearTimeout(timeout);
          func(...args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
      };
    }


    const debouncedInitializeElements = debounce(initializeElements, 300);


    /**
     * Observe dynamic DOM changes and re-init once (common in Shopify themes/apps).
     */
    const observer = new MutationObserver(function (mutations) {
      if (isUpdatingTable) return;


      const now = Date.now();
      if (now - lastInitializedTime < 1000) return; // throttle re-init
      lastInitializedTime = now;


      mutations.forEach(function (mutation) {
        if (mutation.addedNodes.length || mutation.removedNodes.length) {
          observer.disconnect();
          debouncedInitializeElements();
          observer.observe(document.body, { childList: true, subtree: true });
        }
      });
    });


    observer.observe(document.body, { childList: true, subtree: true });
  });


  /**
   * Core: fetch all pages of a customer's entries by EMAIL, then render totals per sweep.
   */
  async function fetchCustomerDetails(email) {
    // Your shop GID. In Shopify Liquid, this resolves to the current shop.
    const platformId = 'gid://shopify/Shop/{{ shop.id }}';


    // Paging settings
    const take = 60;         // page size
    let skip = 0;            // page offset
    const maxFetches = 10;   // hard cap to prevent runaway loops


    // Accumulators
    let allEntries = [];
    let hasMore = true;
    let fetchCount = 0;


    // GraphQL query for email-based lookup
    const query = `
      query QueryCustomer(
        $email: String!,
        $platform: String!,
        $platformId: String!,
        $take: Int,
        $skip: Int
      ) {
        customer(email: $email) {
          entries(platform: $platform, platformId: $platformId, take: $take, skip: $skip) {
            entriesTotal
            sweepstake {
              title
              endDate
            }
          }
          referrer(platform: $platform, platformId: $platformId) {
            referralCode
            usageCount
          }
        }
      }
    `;


    // Variables for each fetch
    const variables = {
      email,
      platform: 'Shopify',
      platformId,
      take,
      skip
    };


    let referrer = null;


    try {
      // Page through results while there appears to be more
      while (hasMore) {
        if (fetchCount >= maxFetches) break;


        variables.skip = skip;


        const response = await fetch('https://backend.cleansweeps.app/graphql', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ query, variables })
        });


        if (!response.ok) {
          throw new Error('Network response was not ok.');
        }


        const { data } = await response.json();
        fetchCount++;


        if (data && data.customer) {
          const fetchedEntries = data.customer.entries;


          if (!Array.isArray(fetchedEntries)) {
            hasMore = false;
            break;
          }


          // Capture referrer only once (first page)
          if (skip === 0 && data.customer.referrer) {
            referrer = data.customer.referrer;
          }


          allEntries = allEntries.concat(fetchedEntries);


          // If the page has fewer than 'take' items, we're done
          if (fetchedEntries.length < take) {
            hasMore = false;
          } else {
            skip += take;
          }
        } else {
          hasMore = false;
        }
      }


      if (allEntries.length > 0) {
        // OPTIONAL UI filter: only show sweeps ending within the last 21 days
        const threeWeeksAgo = Date.now() - 21 * 24 * 60 * 60 * 1000;


        const validEntries = allEntries.filter((entry) => {
          const sweepstakeEndDate = parseInt(entry.sweepstake.endDate, 10);
          return sweepstakeEndDate >= threeWeeksAgo;
        });


        if (validEntries.length > 0) {
          updateTable(validEntries, referrer);
          return;
        }
      }


      displayNoResults();
    } catch (error) {
      displayNoResults();
    }
  }


  /**
   * Render: group entriesTotal by sweepstake title and fill the table.
   */
  function updateTable(entries, referrer) {
    isUpdatingTable = true;


    const tableBody = document.getElementById('sweepstakes-lookup-table-body');
    tableBody.innerHTML = '';


    if (entries.length === 0) {
      displayNoResults();
      isUpdatingTable = false;
      return;
    }


    // Group totals by sweep title
    const sweepstakesMap = {};
    entries.forEach((entry) => {
      const title = entry.sweepstake.title;
      const totalEntries = entry.entriesTotal;


      if (sweepstakesMap[title]) {
        sweepstakesMap[title] += totalEntries;
      } else {
        sweepstakesMap[title] = totalEntries;
      }
    });


    // OPTIONAL: If you want a grand total across all sweeps:
    // const grandTotal = Object.values(sweepstakesMap).reduce((sum, n) => sum + n, 0);


    // Write rows
    Object.keys(sweepstakesMap).forEach((title) => {
      const tr = document.createElement('tr');


      const tdName = document.createElement('td');
      tdName.textContent = title;


      const tdEntries = document.createElement('td');
      tdEntries.textContent = sweepstakesMap[title];


      tr.appendChild(tdName);
      tr.appendChild(tdEntries);
      tableBody.appendChild(tr);
    });


    isUpdatingTable = false;
  }


  /**
   * Render a single row that says “No results”.
   */
  function displayNoResults() {
    isUpdatingTable = true;


    const tableBody = document.getElementById('sweepstakes-lookup-table-body');
    tableBody.innerHTML = '';


    var emptyRow = document.createElement('tr');
    var emptyCell = document.createElement('td');


    emptyCell.setAttribute('colspan', '2');
    emptyCell.textContent = '{{ block.settings.no_results_text }}';


    emptyRow.appendChild(emptyCell);
    tableBody.appendChild(emptyRow);


    isUpdatingTable = false;
  }
</script>

Implementation Checklist

  • Use platform: "Shopify" and your shop’s GID for platformId.
  • For email lookup, send QueryCustomer with { email, platform, platformId, take, skip }.
  • Loop pages until returned array length < take.
  • Sum entriesTotal per sweepstake.title (or reduce to a grand total if desired).
  • (Optional) Apply date filters client-side (e.g., last 21 days) for your UI.