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.
https://backend.cleansweeps.app/graphqlOperation: 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.
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
| Field | Type | Details |
|---|---|---|
email | String! | Required for the email query. Trim and lowercase before lookup. |
platform | String! | Required. Use Shopify. |
platformId | String! | Required. Shop GID: gid://shopify/Shop/<SHOP_ID>. |
take | Int | Page size. This example uses 60. |
skip | Int | Offset. Increase by take while a full page is returned. |
Email lookup result
| Field | Type | Details |
|---|---|---|
entries[].entriesTotal | Number | Entry value returned for each record. Aggregate across all pages for your display. |
entries[].sweepstake.title | String | Sweepstake title; used for grouping in the supplied example. |
entries[].sweepstake.endDate | String | Unix milliseconds in this endpoint’s documentation. |
referrer | Object or null | Optional 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>
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.
<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.

