Product Pricing Case Studies Live Demo

How do I use the Giveaway Ninja API?

Quick answer

Get your Public and Private API keys under SETUP > API in the Giveaway Ninja dashboard and add your domains to Allowed Origins. Use the Private key for REST API calls from your server: addUserEntries, addUserEntriesBatch, getUserInfo and listOrders. Use the Public key for Client API calls in the browser, like getUserInfo and calculateEntries. All requests go to https://api.giveaway.ninja.

Setup

API Keys are available from the SETUP > API menu in your dashboard.

Public API Key — used in client-side (browser) requests. Safe to expose in JavaScript.

Private API Key — used for server-to-server integrations only. Keep this secret.

Allowed Origins must contain the domains you are using the Public API on (comma-separated). For example: myshop.myshopify.com, mycustomdomain.com.

Allowed IPs for the REST API is optional. Leave it empty and REST calls are accepted from any address. Fill it in and calls with your Private key are accepted only from the listed addresses; every other caller is refused with HTTP 403 Forbidden and the body {"status":"IpNotAllowed","message":"Forbidden"}. Refused calls appear in your API log with the address they came from. Entries are IP addresses, CIDR networks or IPv4 ranges, IPv4 or IPv6, separated by commas or new lines. For example: 203.0.113.10, 198.51.100.0/24, 203.0.113.20-203.0.113.40, 2001:db8::/32. The list does not apply to the Client API.

Shopify Checkout & Thank-You pages: Add extensions.shopifycdn.com to Allowed Origins to use the Client API inside Shopify Checkout and Thank-You page extensions.

See how to show earned entries on the checkout and thank-you pages.

API configuration in dashboard

Base URL

All API requests are made to:

https://api.giveaway.ninja

Authentication

Client API endpoints authenticate via the apiKey field in the JSON request body (Public API Key). The server also verifies the Origin header against your Allowed Origins list.

REST API endpoints authenticate via the apiKey field in the JSON request body (Private API Key). No origin check is performed. If you set Allowed IPs for the REST API in the dashboard, the calling address must also be on that list.

Rate Limiting

Limits are counted per endpoint and per calling IP address, in a fixed one-minute window that starts with your first call. Every call counts, including calls rejected for another reason, such as an unknown email.

EndpointLimit (per minute, per IP)
REST addUserEntries120
REST addUserEntriesBatch30 calls of up to 50 entrants each (1,500 entrants per minute)
REST getUserInfo600
REST listOrders60 calls of up to 500 orders each
Client API (each endpoint)240

A request over the limit still gets HTTP 200. The body is the TooManyRequests status and, on the REST API, the number of seconds until the window resets:

{ "status": "TooManyRequests", "retryAfterSeconds": 23 }

There is no HTTP 429 and no Retry-After header. Read the status field of every response, and on TooManyRequests wait retryAfterSeconds (or 60 seconds on the Client API) before sending the same request again.

Running a script from your server? Use the REST API with your Private key. It has the wider limits above and no origin check. The Client API limits are sized for browser traffic from your store. If a campaign needs more than the limits above, contact support with your server's IP address before it starts.
Note: The calculateOrderEntries endpoint skips rate limiting when the origin is extensions.shopifycdn.com, since Shopify checkout extensions may fire multiple requests during the checkout flow.

User-Agent

The API refuses requests whose User-Agent header looks like a crawler or a default scripting client. This applies to every endpoint, Client API and REST API alike. Calls made from a browser on your store are never affected, because the browser sends its own User-Agent.

Calls from your server, a terminal or a test tool are refused when they keep the client's default User-Agent. That includes curl, wget, python-requests, python-urllib, aiohttp, axios, node-fetch, okhttp, Java's built-in client, Apache-HttpClient, Go's net/http, Postman, Scrapy and headless browsers (Puppeteer, Playwright, Selenium). A User-Agent containing bot, spider, crawl or scraper is refused too.

A refused request gets HTTP 403 Forbidden with an "Access is denied" page or {"error":"forbidden"} instead of one of the JSON statuses listed on this page. It never reaches your account, so it doesn't appear in LOGS > API.

The fix is to set a User-Agent that names your integration, for example MyStore-Integration/1.0:

# cURL
curl -A "MyStore-Integration/1.0" ...

# Node.js (fetch or axios)
headers: { 'Content-Type': 'application/json', 'User-Agent': 'MyStore-Integration/1.0' }

# Python (requests)
requests.post(url, json=payload, headers={'User-Agent': 'MyStore-Integration/1.0'})

# PHP (cURL)
curl_setopt($ch, CURLOPT_USERAGENT, 'MyStore-Integration/1.0');
Testing from the command line: a plain curl call returns 403 even with a valid key. Add -A "MyStore-Integration/1.0" (or any User-Agent that names your integration) and you'll get the normal JSON response.

API Logs

All API requests are logged and can be reviewed from your dashboard under LOGS > API.

The logs show request details including the timestamp, IP address, origin, payload content, and response status. This is useful for debugging integration issues and monitoring API usage.

Tip: If you're getting unexpected error responses, check the API logs first — they show the exact payload and status for each request, making it easy to identify issues like missing fields or origin mismatches. A request that gets HTTP 403 without a JSON status never reached your account and won't be listed; see User-Agent.

REST API

The Private API Key is used for server-to-server integrations. Keep it secure and never expose it in client-side code. REST endpoints authenticate with the key alone: there is no origin check, and the limits are wider than the Client API's. As an extra safeguard you can restrict the key to your servers' addresses with Allowed IPs; calls from anywhere else then get HTTP 403 Forbidden.

addUserEntries

Overview

Add entries for a specific user in a giveaway. Use this endpoint to award entries from your own backend logic (e.g. custom purchase flows, CRM events, or third-party integrations). A negative value removes entries.

Prefer no code? Import users and orders from a CSV or send entries to Zapier.

Request

POST /api/server/addUserEntries
ParameterTypeRequiredDescription
apiKey string Required Your Private API Key
giveawayId string Required The giveaway ObjectId
email string Required User's email address
entries integer Required Number of entries to add. Negative to remove. Not zero; at most 1,000,000 either way.
description string Optional Why the entries were awarded. Plain text, up to 500 characters, shown next to the entry in the dashboard. Put your own reference here, such as an order number.

Example Request (cURL)

curl -X POST "https://api.giveaway.ninja/api/server/addUserEntries" \
  -A "MyStore-Integration/1.0" \
  -H "Content-Type: application/json" \
  -d '{
    "apiKey": "your_private_api_key",
    "giveawayId": "your_giveaway_id",
    "email": "user@example.com",
    "entries": 10,
    "description": "Bonus entries for VIP customer"
  }'

Response

{
  "status": "Success",
  "data": {
    "totalEntries": 135,
    "saleValue": 487.50,
    "emailConfirmed": "true",
    "referralId": "XYZ1234",
    "referralCount": 8
  }
}

totalEntries is the user's balance after this call.

Each call adds again. addUserEntries is not idempotent: sending the same request twice awards the entries twice. If you retry after a timeout or run a scheduled job, keep a record of what you have already sent, and check apiEntries in the REST getUserInfo response before sending a correction again. A negative value never takes a user below zero entries; the response shows what was actually applied.

Error Responses

StatusDescription
PayloadMissingRequest body is empty or malformed
TooManyRequestsRate limit exceeded; see retryAfterSeconds
MissingApiKeyThe apiKey field is missing
InvalidApiKeyThe key does not match any account (check you are using the Private key)
IpNotAllowedHTTP 403 Forbidden. Your account has Allowed IPs set and this call did not come from one of them. Your API log shows the address seen
EntryValueMissingOrZeroentries is missing, zero or beyond 1,000,000
MissingGivewayIdThe giveawayId field is missing
EmailMissingThe email field is missing
GiveawayOrUserNotFoundThe giveaway is not yours, or no user with this email entered it

Tip: Use https://reqbin.com/curl to run quick tests.


addUserEntriesBatch

Overview

The same as addUserEntries, for up to 50 users of one giveaway in a single call. Use it when a job corrects many users at once. Each user is processed on its own: one unknown email does not stop the others, and the response tells you what happened to each one.

Request

POST /api/server/addUserEntriesBatch
ParameterTypeRequiredDescription
apiKey string Required Your Private API Key
giveawayId string Required The giveaway ObjectId
users array Required 1 to 50 objects, each with email (required), entries (required, not zero, negative to remove) and description (optional). Applied in list order; a duplicate email is applied twice.

Example Request (cURL)

curl -X POST "https://api.giveaway.ninja/api/server/addUserEntriesBatch" \
  -A "MyStore-Integration/1.0" \
  -H "Content-Type: application/json" \
  -d '{
    "apiKey": "your_private_api_key",
    "giveawayId": "your_giveaway_id",
    "users": [
      { "email": "anna@example.com", "entries": 140, "description": "FX correction order #1001 (CAD)" },
      { "email": "ben@example.com", "entries": -20, "description": "FX correction order #1002 (GBP)" },
      { "email": "nobody@example.com", "entries": 5 }
    ]
  }'

Response

{
  "status": "Success",
  "data": {
    "processed": 3,
    "succeeded": 2,
    "failed": 1,
    "entriesApplied": 120,
    "results": [
      { "email": "anna@example.com", "status": "Success", "entries": 140, "totalEntries": 640 },
      { "email": "ben@example.com", "status": "Success", "entries": -20, "totalEntries": 80 },
      { "email": "nobody@example.com", "status": "GiveawayOrUserNotFound" }
    ]
  }
}
FieldTypeDescription
processedintegerNumber of users in the request
succeededintegerUsers whose entries were applied
failedintegerUsers that were skipped (see their status)
entriesAppliedintegerSum of the entries actually applied, after clamping at zero
results[].emailstringThe email as processed (lower-cased and trimmed)
results[].statusstringSuccess, EmailMissing, EntryValueMissingOrZero or GiveawayOrUserNotFound
results[].entriesintegerEntries applied to this user (on Success)
results[].totalEntriesintegerThe user's balance after this call (on Success)

The top-level status is Success whenever the request itself was accepted, even if every user failed. Always read results.

Error Responses

StatusDescription
PayloadMissingRequest body is empty or malformed
TooManyRequestsRate limit exceeded; see retryAfterSeconds
MissingApiKeyThe apiKey field is missing
InvalidApiKeyThe key does not match any account
IpNotAllowedHTTP 403 Forbidden. Your account has Allowed IPs set and this call did not come from one of them. Your API log shows the address seen
MissingGivewayIdThe giveawayId field is missing
UsersMissingOrTooManyusers is missing, empty or has more than 50 items. Nothing was applied.
GiveawayOrUserNotFoundThe giveaway is not yours. Nothing was applied.

getUserInfo

Overview

The server-side version of the Client API getUserInfo: the same user lookup with your Private key, a limit of 600 calls per minute and no origin check. Use it from scripts and backend jobs. On top of the Client API fields it returns apiEntries, the history of entries added or removed through the REST API, so a job can check what it has already sent before sending a correction again.

Request

POST /api/server/getUserInfo
ParameterTypeRequiredDescription
apiKey string Required Your Private API Key
giveawayId string Required The giveaway ObjectId
email string Required User's email address (also accepts phone number)

Example Request (cURL)

curl -X POST "https://api.giveaway.ninja/api/server/getUserInfo" \
  -A "MyStore-Integration/1.0" \
  -H "Content-Type: application/json" \
  -d '{
    "apiKey": "your_private_api_key",
    "giveawayId": "your_giveaway_id",
    "email": "user@example.com"
  }'

Success Response

{
  "status": "Success",
  "data": {
    "email": "user@example.com",
    "totalEntries": 640,
    "saleValue": 487.50,
    "saleCurrency": "USD",
    "emailConfirmed": "true",
    "referralId": "XYZ1234",
    "referralCount": 8,
    "orders": [
      {
        "orderId": "5001",
        "orderPoints": "360",
        "validation": "{\"OrderId\":\"5001\",\"OrderSource\":\"shopify\",\"OrderPoints\":360,\"TotalPoints\":360,\"AttributionMethod\":\"subtotal\", ...}",
        "date": "2026-11-12T14:03:22.1234567Z"
      }
    ],
    "apiEntries": [
      { "entries": 140, "description": "FX correction order #5001 (CAD)", "date": "2026-11-12T14:15:02.0000000Z" }
    ]
  }
}
FieldTypeDescription
emailstringThe user's email as stored
totalEntriesintegerTotal entries earned by the user
saleValuedecimalTotal sale value attributed to the user
saleCurrencystringCurrency of saleValue (your store currency)
emailConfirmedstring"true" if the user clicked the confirmation email
referralIdstringReferral code assigned to the user
referralCountintegerNumber of users referred
ordersarrayPurchase orders with entry details, oldest first. Same fields as the Client API, plus date (ISO 8601, UTC) of when the order was processed
apiEntriesarrayEntries added or removed through addUserEntries and addUserEntriesBatch, oldest first: entries as applied, your description and the date

Error Responses

StatusDescription
PayloadMissingRequest body is empty or malformed
TooManyRequestsRate limit exceeded; see retryAfterSeconds
MissingApiKeyThe apiKey field is missing
InvalidApiKeyThe key does not match any account
IpNotAllowedHTTP 403 Forbidden. Your account has Allowed IPs set and this call did not come from one of them. Your API log shows the address seen
MissingGivewayIdThe giveawayId field is missing
EmailMissingThe email field is missing
GiveawayOrUserNotFoundThe giveaway is not yours, or no user with this email or phone entered it

listOrders

Overview

The orders Giveaway Ninja has processed for your account since a point in time, oldest first, with the entries each one earned. Use it to reconcile your own records against ours without calling getUserInfo once per customer. Results are paged: follow nextCursor until hasMore is false.

Request

POST /api/server/listOrders
ParameterTypeRequiredDescription
apiKey string Required Your Private API Key
since string Required Return orders processed at or after this time. ISO 8601, for example 2026-11-30T00:00:00Z. A value without a time zone is read as UTC.
until string Optional Return orders processed at or before this time. Same format.
giveawayId string Optional Only orders of this giveaway. Omit for all giveaways of your account, including orders that matched no giveaway.
status string Optional Only orders with this status, for example entries. See the status table below.
limit integer Optional Orders per page. Default 100, maximum 500.
cursor string Optional The nextCursor value from the previous page. Send the other parameters unchanged.

Example Request (cURL)

curl -X POST "https://api.giveaway.ninja/api/server/listOrders" \
  -A "MyStore-Integration/1.0" \
  -H "Content-Type: application/json" \
  -d '{
    "apiKey": "your_private_api_key",
    "giveawayId": "your_giveaway_id",
    "since": "2026-11-30T00:00:00Z",
    "limit": 200
  }'

Success Response

{
  "status": "Success",
  "data": {
    "count": 2,
    "hasMore": true,
    "nextCursor": "639012345678901234-672a1b2c3d4e5f6a7b8c9d0e",
    "orders": [
      {
        "id": "672a1b2c3d4e5f6a7b8c9d0d",
        "orderId": "5001",
        "date": "2026-11-30T08:12:41.0000000Z",
        "status": "entries",
        "email": "anna@example.com",
        "giveawayId": "your_giveaway_id",
        "entries": 360,
        "vipSignupBonusEntries": 0,
        "orderTotal": 72.00,
        "currency": "USD",
        "source": "web",
        "giftCardAmount": 0,
        "validation": "{\"OrderId\":\"5001\",\"OrderPoints\":360,\"TotalPoints\":360,\"AttributionMethod\":\"subtotal\", ...}"
      },
      {
        "id": "672a1b2c3d4e5f6a7b8c9d0e",
        "orderId": "5002",
        "date": "2026-11-30T08:14:02.0000000Z",
        "status": "no-user",
        "email": "guest@example.com",
        "giveawayId": "",
        "entries": 0,
        "vipSignupBonusEntries": 0,
        "orderTotal": 40.00,
        "currency": "USD",
        "source": "web",
        "giftCardAmount": 0,
        "validation": "No matching giveaway user found"
      }
    ]
  }
}
FieldTypeDescription
countintegerOrders in this page
hasMorebooleantrue when another page follows
nextCursorstringPass as cursor to get the next page. Empty on the last page.
orders[].idstringOur record id. One order can have several records: the original, then a cancellation or rollback.
orders[].orderIdstringThe order number from your store
orders[].datestringWhen we processed it (ISO 8601, UTC). This is what since and until filter on, not the time the order was placed.
orders[].statusstringSee the table below
orders[].emailstringCustomer email on the order
orders[].giveawayIdstringThe giveaway credited; empty when no entrant matched
orders[].entriesintegerEntries awarded for the order (0 when none)
orders[].vipSignupBonusEntriesintegerVIP sign-up bonus granted while processing this order, kept apart from the order's own entries
orders[].orderTotaldecimalOrder total as received, in currency (your store currency)
orders[].sourcestringWhere the order was placed, for example web or pos
orders[].giftCardAmountdecimalGift card amount used on the order
orders[].validationstringThe entry calculation details, the same JSON text as orders[].validation in getUserInfo, or a short note for orders that earned nothing

Order statuses

StatusMeaning
entriesAn entrant matched and the order earned entries
no-entriesAn entrant matched but your rules awarded 0 entries (outside the dates, under the minimum, excluded products)
no-userNo entrant with the order's email; nothing awarded
processedImported or re-processed from the dashboard
cancelledA cancellation record: the order's entries were removed
rolled-back-edit, rolled-back-cancelled, rolled-back-reimportThe original record after its entries were taken back, because the order was edited, cancelled or re-imported

Error Responses

StatusDescription
PayloadMissingRequest body is empty or malformed
TooManyRequestsRate limit exceeded; see retryAfterSeconds
MissingApiKeyThe apiKey field is missing
InvalidApiKeyThe key does not match any account
IpNotAllowedHTTP 403 Forbidden. Your account has Allowed IPs set and this call did not come from one of them. Your API log shows the address seen
InvalidParametersince is missing or not a date, or until or cursor could not be read
GiveawayOrUserNotFoundgiveawayId was given and is not one of your giveaways

Node.js Example (walk every page)

async function listOrdersSince(since) {
  const all = [];
  let cursor = '';
  do {
    const res = await fetch('https://api.giveaway.ninja/api/server/listOrders', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'User-Agent': 'MyStore-Integration/1.0' },
      body: JSON.stringify({ apiKey: process.env.GN_PRIVATE_KEY, giveawayId: 'YOUR_GIVEAWAY_ID', since, limit: 500, cursor })
    });
    const body = await res.json();
    if (body.status === 'TooManyRequests') {
      await new Promise(r => setTimeout(r, (body.retryAfterSeconds || 60) * 1000));
      continue;
    }
    if (body.status !== 'Success') throw new Error('listOrders: ' + body.status);
    all.push(...body.data.orders);
    cursor = body.data.hasMore ? body.data.nextCursor : '';
  } while (cursor);
  return all;
}

getUserInfo

Overview

Retrieve a user's giveaway data including total entries, referral information, sale value, and a breakdown of all purchase orders with their entry calculations. Use this when you want to display a user's giveaway status on your storefront.

Request

POST /api/client/getUserInfo
ParameterTypeRequiredDescription
giveawayId string Required The giveaway ObjectId
apiKey string Required Your Public API Key
email string Required User's email address (also accepts phone number)

Success Response

{
  "status": "Success",
  "data": {
    "totalEntries": 125,
    "saleValue": 487.50,
    "emailConfirmed": "true",
    "referralId": "XYZ1234",
    "referralCount": 8,
    "orders": [
      {
        "orderId": "5001",
        "orderPoints": "50",
        "validation": "{\"OrderId\":\"5001\",\"OrderSource\":\"shopify\",\"OrderSourceResult\":{\"Description\":\"Valid order\",\"IsValid\":true},\"OrderPoints\":50,\"OrderPointsDescription\":\"Entries per currency spent: 1 points per 3 USD (subtotal)\",\"BundleRulePoints\":0,\"BundleRuleResults\":[],\"PurchaseRulePoints\":10,\"TotalPoints\":60,\"PurchasePeriodResult\":null,\"AovBoosterResult\":null,\"OrderDateRangeResult\":{\"Description\":\"Order within valid date range\",\"IsValid\":true},\"MinimumOrderValueResult\":{\"Description\":\"Minimum order value reached\",\"IsValid\":true},\"AttributionMethod\":\"subtotal\",\"PurchaseRuleResults\":[\"Product X matched: +10 points\"],\"GiftCardAmount\":null,\"GiftCardInfo\":null}"
      }
    ]
  }
}
FieldTypeDescription
totalEntriesintegerTotal entries earned by the user
saleValuedecimalTotal sale value attributed to the user
emailConfirmedstring"true" if the user clicked the confirmation email
referralIdstringReferral code assigned to the user (use with ?njref= querystring)
referralCountintegerNumber of users referred
ordersarrayList of purchase orders with entry details
orders[].orderIdstringThe Shopify order ID
orders[].orderPointsstringPoints earned from this order
orders[].validationstring (JSON)Detailed entry calculation breakdown (TotalPoints, PurchaseRuleResults, BundleRuleResults, etc.)

Error Responses

StatusDescription
PayloadMissingRequest body is empty or malformed
TooManyRequestsRate limit exceeded (240 requests/min per IP). Calling from a server? Use the REST getUserInfo.
MissingApiKeyThe apiKey field is missing
InvalidApiKeyThe API key does not match any subscription
OriginNotAllowedThe request origin is not in your Allowed Origins list
MissingGivewayIdThe giveawayId field is missing
EmailMissingThe email field is missing
GiveawayOrUserNotFoundNo user found with this email in the specified giveaway

JavaScript Demo

fetch('https://api.giveaway.ninja/api/client/getUserInfo', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    giveawayId: 'YOUR_GIVEAWAY_ID',
    apiKey: 'YOUR_PUBLIC_API_KEY',
    email: 'customer@example.com'
  })
})
.then(response => response.json())
.then(data => {
  if (data.status === 'Success') {
    console.log('Total entries:', data.data.totalEntries);
    console.log('Referral code:', data.data.referralId);
    console.log('Orders:', data.data.orders);
  } else {
    console.error('API error:', data.status);
  }
})
.catch(error => console.error('Request failed:', error));

HTML Demo

<div id="giveaway-info">
  <p>Loading...</p>
</div>

<script>
(function() {
  var container = document.getElementById('giveaway-info');

  fetch('https://api.giveaway.ninja/api/client/getUserInfo', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      giveawayId: 'YOUR_GIVEAWAY_ID',
      apiKey: 'YOUR_PUBLIC_API_KEY',
      email: '{{ customer.email }}'  // Shopify Liquid variable
    })
  })
  .then(function(r) { return r.json(); })
  .then(function(data) {
    if (data.status === 'Success') {
      var d = data.data;
      container.innerHTML =
        '<p><strong>Total Entries:</strong> ' + d.totalEntries + '</p>' +
        '<p><strong>Sale Value:</strong> $' + d.saleValue.toFixed(2) + '</p>' +
        '<p><strong>Referral Code:</strong> ' + d.referralId + '</p>' +
        '<p><strong>Referrals:</strong> ' + d.referralCount + '</p>';
    } else if (data.status === 'GiveawayOrUserNotFound') {
      container.innerHTML = '<p>No giveaway data found for this user.</p>';
    } else {
      container.innerHTML = '<p>Error: ' + data.status + '</p>';
    }
  })
  .catch(function(err) {
    container.innerHTML = '<p>Could not load giveaway data.</p>';
  });
})();
</script>

calculateEntries

Overview

Calculate the number of entries a single product would earn based on the giveaway's purchase rules, bundle rules, time multipliers, and AOV boosters. Use this on product pages to show customers how many entries they'll earn before purchasing.

Request

POST /api/client/calculateEntries
ParameterTypeRequiredDescription
giveawayId string Required The giveaway ObjectId
apiKey string Required Your Public API Key
productId string Required Shopify product ID
variantId string Required Shopify variant ID
quantity integer Required Quantity (must be > 0)
price integer Required Unit price in cents (e.g. $50.00 = 5000)

Success Response

{
  "status": "Success",
  "data": {
    "totalEntries": 15,
    "ruleType": "quantity",
    "ruleEntries": 5,
    "ruleQuantity": 1,
    "ruleAmount": 0,
    "multiplier": 2,
    "orderRuleEntries": 10,
    "orderRuleAmount": 1,
    "hasOrderRule": true,
    "rewardAmountSpent": 1,
    "isExpired": false,
    "bonusOrderEntries": 50,
    "bonusOrderEntriesOver": 100.00,
    "aovMultiplier": 1,
    "isAovBooster": false,
    "aovExcludeFromOrderTotal": false
  }
}
FieldTypeDescription
totalEntriesintegerTotal product entries calculated
ruleTypestring|nullProduct rule type: "quantity" or "amount", or null if no product rule
ruleEntriesintegerEntries awarded per rule threshold
ruleQuantityintegerQuantity threshold (for quantity-based rules)
ruleAmountdecimalAmount threshold (for amount-based rules)
multiplierintegerActive time booster multiplier (1 = no boost)
orderRuleEntriesintegerBase entries from order-level rule
orderRuleAmountdecimalOrder rule amount threshold (0 = fixed mode)
hasOrderRulebooleanWhether order-level rules apply
rewardAmountSpentintegerReward mode: 0 = fixed per order, 1 = per amount spent, 2 = product rules only
isExpiredbooleantrue if the giveaway is not active
bonusOrderEntriesintegerBonus entries for orders over a threshold
bonusOrderEntriesOverdecimalOrder value threshold for bonus entries
aovMultiplierintegerAOV booster multiplier (if product is a booster)
isAovBoosterbooleantrue if this product is an AOV booster
aovExcludeFromOrderTotalbooleantrue if this booster's value is excluded from order total calculations

Error Responses

StatusDescription
PayloadMissingRequest body is empty or malformed
TooManyRequestsRate limit exceeded
MissingApiKeyThe apiKey field is missing
InvalidApiKeyThe API key does not match any subscription
OriginNotAllowedThe request origin is not in your Allowed Origins list
MissingGiveawayIdThe giveawayId field is missing
MissingProductIdThe productId field is missing
MissingVariantIdThe variantId field is missing
InvalidQuantityQuantity must be greater than 0
InvalidPricePrice cannot be negative
GiveawayNotFoundNo giveaway found with the given ID
NoBoostSalesActionThe giveaway does not have purchase entries enabled
InvalidProductIdThe product ID is not a valid number
InvalidVariantIdThe variant ID is not a valid number
ValidationFailedProduct did not match any entry rule (response includes data with zero entries)

JavaScript Demo

// Calculate entries for a single product
// price = unit price in cents (Shopify format: $50.00 = 5000)
fetch('https://api.giveaway.ninja/api/client/calculateEntries', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    giveawayId: 'YOUR_GIVEAWAY_ID',
    apiKey: 'YOUR_PUBLIC_API_KEY',
    productId: '7654321',
    variantId: '43210987',
    quantity: 1,
    price: 5000  // $50.00 in cents
  })
})
.then(response => response.json())
.then(data => {
  if (data.status === 'Success') {
    console.log('Entries for this product:', data.data.totalEntries);
    console.log('Rule type:', data.data.ruleType);
    console.log('Time multiplier:', data.data.multiplier + 'x');

    if (data.data.isExpired) {
      console.log('Giveaway has ended');
    }
  } else {
    console.error('API error:', data.status);
  }
})
.catch(error => console.error('Request failed:', error));

HTML Demo

<div id="entries-badge" style="display:none; padding:10px; background:#f0fff0; border:1px solid #ccc; border-radius:6px;">
  <span id="entries-count"></span> entries with this product
</div>

<script>
(function() {
  // Replace with your actual values or use Shopify Liquid variables
  var productId = '{{ product.id }}';
  var variantId = '{{ product.selected_or_first_available_variant.id }}';
  var price = {{ product.selected_or_first_available_variant.price }};  // Already in cents in Liquid

  fetch('https://api.giveaway.ninja/api/client/calculateEntries', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      giveawayId: 'YOUR_GIVEAWAY_ID',
      apiKey: 'YOUR_PUBLIC_API_KEY',
      productId: productId,
      variantId: variantId,
      quantity: 1,
      price: price
    })
  })
  .then(function(r) { return r.json(); })
  .then(function(data) {
    if (data.status === 'Success' && data.data.totalEntries > 0) {
      document.getElementById('entries-count').textContent = data.data.totalEntries;
      document.getElementById('entries-badge').style.display = 'block';
    }
  })
  .catch(function(err) {
    console.error('Failed to load entries:', err);
  });
})();
</script>

calculateEntriesBatch

Overview

Calculate entries for multiple products in a single request. Use this on collection pages or grids to efficiently fetch entry data for all visible products at once, instead of making individual API calls per product.

Request

POST /api/client/calculateEntriesBatch
ParameterTypeRequiredDescription
giveawayId string Required The giveaway ObjectId
apiKey string Required Your Public API Key
products array Required Array of product objects (see below)
products[] object:
ParameterTypeRequiredDescription
productId string Required Shopify product ID
variantId string Required Shopify variant ID
quantity integer Required Quantity (must be > 0)
price integer Required Unit price in cents (e.g. $50.00 = 5000)

Success Response

{
  "status": "Success",
  "data": [
    {
      "productId": "7654321",
      "variantId": "43210987",
      "status": "Success",
      "totalEntries": 15,
      "ruleType": "quantity",
      "ruleEntries": 5,
      "ruleQuantity": 1,
      "ruleAmount": 0,
      "multiplier": 2,
      "orderRuleEntries": 10,
      "orderRuleAmount": 1,
      "hasOrderRule": true,
      "rewardAmountSpent": 1,
      "isExpired": false,
      "bonusOrderEntries": 50,
      "bonusOrderEntriesOver": 100.00,
      "aovMultiplier": 1,
      "isAovBooster": false,
      "aovExcludeFromOrderTotal": false
    }
  ]
}

The data array contains one result per product, in the same order as the request. Each result includes all fields from calculateEntries plus productId, variantId, and an individual status per product.

Per-product status values: Success, Giveaway-Expired, ValidationFailed, InvalidProductId, or InvalidVariantId. The top-level status is Success as long as the batch request itself is valid.

Error Responses

StatusDescription
PayloadMissingRequest body is empty or malformed
TooManyRequestsRate limit exceeded
MissingApiKeyThe apiKey field is missing
InvalidApiKeyThe API key does not match any subscription
OriginNotAllowedThe request origin is not in your Allowed Origins list
MissingGiveawayIdThe giveawayId field is missing
MissingProductsThe products array is missing or empty
MissingProductIdA product in the array is missing productId
MissingVariantIdA product in the array is missing variantId
InvalidQuantityA product has quantity <= 0
InvalidPriceA product has negative price
GiveawayNotFoundNo giveaway found with the given ID
NoBoostSalesActionThe giveaway does not have purchase entries enabled

JavaScript Demo

// Calculate entries for multiple products in one request
// Ideal for collection pages to avoid multiple API calls
fetch('https://api.giveaway.ninja/api/client/calculateEntriesBatch', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    giveawayId: 'YOUR_GIVEAWAY_ID',
    apiKey: 'YOUR_PUBLIC_API_KEY',
    products: [
      { productId: '111', variantId: '222', quantity: 1, price: 2500 },
      { productId: '333', variantId: '444', quantity: 1, price: 4900 },
      { productId: '555', variantId: '666', quantity: 1, price: 7500 }
    ]
  })
})
.then(response => response.json())
.then(data => {
  if (data.status === 'Success') {
    data.data.forEach(function(product) {
      console.log('Product ' + product.productId + ': ' +
        product.totalEntries + ' entries (' + product.status + ')');
    });
  }
})
.catch(error => console.error('Request failed:', error));

HTML Demo

<!-- Place badges inside your product grid items -->
<div class="product-grid">
  <div class="product-card" data-product-id="111" data-variant-id="222" data-price="2500">
    <p>Product A - $25.00</p>
    <span class="entry-badge" style="display:none;"></span>
  </div>
  <div class="product-card" data-product-id="333" data-variant-id="444" data-price="4900">
    <p>Product B - $49.00</p>
    <span class="entry-badge" style="display:none;"></span>
  </div>
</div>

<script>
(function() {
  var cards = document.querySelectorAll('.product-card');
  var products = [];

  cards.forEach(function(card) {
    products.push({
      productId: card.dataset.productId,
      variantId: card.dataset.variantId,
      quantity: 1,
      price: parseInt(card.dataset.price)
    });
  });

  fetch('https://api.giveaway.ninja/api/client/calculateEntriesBatch', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      giveawayId: 'YOUR_GIVEAWAY_ID',
      apiKey: 'YOUR_PUBLIC_API_KEY',
      products: products
    })
  })
  .then(function(r) { return r.json(); })
  .then(function(data) {
    if (data.status === 'Success') {
      data.data.forEach(function(result, i) {
        if (result.status === 'Success' && result.totalEntries > 0) {
          var badge = cards[i].querySelector('.entry-badge');
          badge.textContent = result.totalEntries + ' entries';
          badge.style.display = 'inline-block';
        }
      });
    }
  })
  .catch(function(err) { console.error('Batch request failed:', err); });
})();
</script>

calculateOrderEntries

Overview

Calculate the total entries an entire cart/order would earn, including order-level points, product-specific purchase rules, bundle rules, time multipliers, AOV boosters, and minimum order requirements. Designed for checkout and cart pages to show customers the total entries they'll earn for the entire order.

See how purchase-based giveaway entries work for the rules this endpoint applies.

Request

POST /api/client/calculateOrderEntries
ParameterTypeRequiredDescription
giveawayId string Required The giveaway ObjectId
apiKey string Required Your Public API Key
cartData object Required Cart data object (see below)
cartData object:
ParameterTypeRequiredDescription
items array Required Array of cart items
subtotal integer Required Cart subtotal in cents (before discounts, shipping, taxes)
total integer Required Cart total in cents (after discounts, before shipping/taxes)
currency string Optional Currency code (e.g. "USD")
cartData.items[] object:
ParameterTypeRequiredDescription
productId string Required Shopify product ID
variantId string Required Shopify variant ID
quantity integer Required Quantity of this item in the cart
price integer Required Line total in cents (unit price × quantity, e.g. 2 items at $25 = 5000)
Price format note: The price field in cartData.items is the line total (unit price × quantity) in cents, not the unit price. This differs from calculateEntries where price is the unit price.

Success Response

{
  "status": "Success",
  "data": {
    "totalEntries": 45,
    "orderPoints": 20,
    "bundleRulePoints": 10,
    "purchaseRulePoints": 15,
    "multiplier": 1,
    "aovMultiplier": 1,
    "calculationBase": 120.00,
    "attributionMode": "subtotal",
    "isExpired": false,
    "hasMinimumOrder": true,
    "minimumOrderValue": 50.00,
    "minimumOrderReached": true
  }
}
FieldTypeDescription
totalEntriesintegerTotal entries for the entire order (0 if minimum order not reached)
orderPointsintegerPoints from the order-level rule (based on order value or fixed)
bundleRulePointsintegerPoints from matched bundle rules
purchaseRulePointsintegerPoints from product-specific purchase rules
multiplierintegerActive time booster multiplier (1 = no boost)
aovMultiplierintegerAOV booster multiplier (stackable across matching products)
calculationBasedecimalOrder value used for calculation (after deducting excluded products)
attributionModestring"subtotal" or "total" — which order value is used
isExpiredbooleantrue if the giveaway is not active
hasMinimumOrderbooleantrue if a minimum order value is configured
minimumOrderValuedecimalMinimum order value required to earn entries
minimumOrderReachedbooleantrue if the current order meets the minimum

Error Responses

StatusDescription
PayloadMissingRequest body is empty or malformed
TooManyRequestsRate limit exceeded
MissingApiKeyThe apiKey field is missing
InvalidApiKeyThe API key does not match any subscription
OriginNotAllowedThe request origin is not in your Allowed Origins list
MissingGiveawayIdThe giveawayId field is missing
GiveawayNotFoundNo giveaway found with the given ID
NoBoostSalesActionThe giveaway does not have purchase entries enabled
Empty cart: If cartData is null or has no items, the API returns a Success response with totalEntries: 0 rather than an error. This lets you safely call the API even before the customer has added items.

JavaScript Demo

// Calculate total order entries from Shopify cart data
// Fetch the current cart, then call the API
fetch('/cart.js')
  .then(response => response.json())
  .then(cart => {
    var cartData = {
      items: cart.items.map(item => ({
        productId: String(item.product_id),
        variantId: String(item.variant_id),
        quantity: item.quantity,
        price: item.final_line_price  // Line total in cents
      })),
      subtotal: cart.items_subtotal_price,
      total: cart.total_price,
      currency: cart.currency
    };

    return fetch('https://api.giveaway.ninja/api/client/calculateOrderEntries', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        giveawayId: 'YOUR_GIVEAWAY_ID',
        apiKey: 'YOUR_PUBLIC_API_KEY',
        cartData: cartData
      })
    });
  })
  .then(response => response.json())
  .then(data => {
    if (data.status === 'Success') {
      console.log('Total entries for this order:', data.data.totalEntries);
      console.log('Order points:', data.data.orderPoints);
      console.log('Bundle points:', data.data.bundleRulePoints);
      console.log('Purchase rule points:', data.data.purchaseRulePoints);

      if (data.data.hasMinimumOrder && !data.data.minimumOrderReached) {
        console.log('Minimum order of $' +
          data.data.minimumOrderValue.toFixed(2) + ' not reached');
      }
    }
  })
  .catch(error => console.error('Request failed:', error));

HTML Demo

<div id="order-entries-banner" style="display:none; padding:12px; background:#e8f5e8; border:1px solid #c3e6c3; border-radius:6px; margin:10px 0;">
  <span id="order-entries-text"></span>
</div>

<script>
(function() {
  var banner = document.getElementById('order-entries-banner');
  var text = document.getElementById('order-entries-text');

  // Fetch the Shopify cart
  fetch('/cart.js')
    .then(function(r) { return r.json(); })
    .then(function(cart) {
      if (!cart.items || cart.items.length === 0) return;

      var cartData = {
        items: cart.items.map(function(item) {
          return {
            productId: String(item.product_id),
            variantId: String(item.variant_id),
            quantity: item.quantity,
            price: item.final_line_price
          };
        }),
        subtotal: cart.items_subtotal_price,
        total: cart.total_price,
        currency: cart.currency
      };

      return fetch('https://api.giveaway.ninja/api/client/calculateOrderEntries', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          giveawayId: 'YOUR_GIVEAWAY_ID',
          apiKey: 'YOUR_PUBLIC_API_KEY',
          cartData: cartData
        })
      });
    })
    .then(function(r) { return r ? r.json() : null; })
    .then(function(data) {
      if (!data || data.status !== 'Success') return;
      var d = data.data;

      if (d.hasMinimumOrder && !d.minimumOrderReached) {
        text.textContent = 'Add $' +
          (d.minimumOrderValue - d.calculationBase).toFixed(2) +
          ' more to earn giveaway entries!';
        banner.style.background = '#fff8e1';
        banner.style.borderColor = '#ffe082';
        banner.style.display = 'block';
      } else if (d.totalEntries > 0) {
        var label = d.totalEntries === 1 ? ' entry' : ' entries';
        text.textContent = d.totalEntries + label + ' with this order!';
        banner.style.display = 'block';
      }
    })
    .catch(function(err) {
      console.error('Order entries error:', err);
    });
})();
</script>

AI Integration

Use AI coding assistants like Claude, ChatGPT, or GitHub Copilot to help you build your GiveawayNinja API integration faster.

Download the API reference file below and pass it to your AI assistant so it has full context about all endpoints, request/response formats, and error codes.

Download API Reference

How to use

  1. Download the .md file above
  2. Open your AI assistant (Claude, ChatGPT, Copilot, etc.)
  3. Upload or paste the file into the conversation
  4. Ask it to help you build your integration — for example: "Build me a Shopify section that shows how many giveaway entries the customer has earned"

People also ask

What's the difference between the Public and Private API keys?

The Public key is for browser calls to the Client API and is checked against your Allowed Origins. The Private key is for server-to-server REST calls and must stay secret. See how to send giveaway entries to Zapier for a no-code option.

Is there a rate limit?

Yes. Limits are per endpoint and per IP address, in a one-minute window: 120 calls for addUserEntries, 30 for addUserEntriesBatch, 600 for the REST getUserInfo, 60 for listOrders and 240 for each Client API endpoint. Above the limit you get TooManyRequests with retryAfterSeconds. Server scripts should use the REST API, which has the wider limits. See Rate Limiting.

How do I debug a failing API call?

Open LOGS > API in your dashboard. Each request shows its time, IP, origin, payload and response status. For order-related calls, compare with the order log statuses.

Why does the API return 403 Forbidden or an "Access is denied" page?

The request was refused by our bot filter because its User-Agent looks like a crawler or a default scripting client, such as curl, python-requests, axios, node-fetch or Postman. Send a User-Agent that names your integration, for example MyStore-Integration/1.0, and avoid the words bot, spider, crawl and scraper. Refused requests never reach your account, so they don't appear in LOGS > API. See User-Agent.

Can I use the API in Shopify checkout?

Yes. Add extensions.shopifycdn.com to Allowed Origins. See how to show earned entries on the checkout and thank-you pages.



 Need API Help?



🍪