# GiveawayNinja API Reference

> Download this file and pass it to Claude, ChatGPT, or any AI coding assistant so it can help you integrate with the GiveawayNinja API.

## Base URL

```
https://api.giveaway.ninja
```

## Authentication

- **Client API** (browser-safe): Authenticate via the `apiKey` field in the JSON body using your **Public API Key**. The server also checks the `Origin` header against your Allowed Origins list.
- **REST API** (server-to-server): Authenticate via the `apiKey` field in the JSON body using your **Private API Key**. No origin check.

API Keys are available from **SETUP > API** in your dashboard.

**Allowed Origins** must list every domain that will call the Client API (comma-separated).
For Shopify Checkout / Thank-You page extensions, add `extensions.shopifycdn.com`.

## Rate Limiting

Client API endpoints: **15 requests per minute** per IP address.
Exceeding the limit returns `TooManyRequests`.

> The `calculateOrderEntries` endpoint skips rate limiting when the origin is `extensions.shopifycdn.com`.

## API Logs

All requests are logged under **LOGS > API** in the dashboard (timestamp, IP, origin, payload, response status).

---

## REST API

### POST /api/server/addUserEntries

Add entries for a specific user in a giveaway from your backend.

#### Request Body

| Parameter   | Type    | Required | Description                              |
|-------------|---------|----------|------------------------------------------|
| apiKey      | string  | Yes      | Your Private API Key                     |
| giveawayId  | string  | Yes      | The giveaway ObjectId                    |
| email       | string  | Yes      | User's email address                     |
| entries     | integer | Yes      | Number of entries to add                 |
| description | string  | No       | Description of why entries were awarded  |

#### Example (cURL)

```bash
curl -X POST "https://api.giveaway.ninja/api/server/addUserEntries" \
  -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

Returns the same structure as `getUserInfo` (see below), including the user's updated total entries.

---

## Client API

### POST /api/client/getUserInfo

Retrieve a user's giveaway data: total entries, referral info, sale value, and purchase-order breakdown.

#### Request Body

| Parameter  | Type   | Required | Description                                 |
|------------|--------|----------|---------------------------------------------|
| giveawayId | string | Yes      | The giveaway ObjectId                       |
| apiKey     | string | Yes      | Your Public API Key                         |
| email      | string | Yes      | User's email address (also accepts phone number) |

#### Success Response

```json
{
  "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}"
      }
    ]
  }
}
```

#### Response Fields

| Field                | Type         | Description                                                                 |
|----------------------|--------------|-----------------------------------------------------------------------------|
| totalEntries         | integer      | Total entries earned by the user                                            |
| saleValue            | decimal      | Total sale value attributed to the user                                     |
| emailConfirmed       | string       | `"true"` if the user clicked the confirmation email                         |
| referralId           | string       | Referral code assigned to the user (use with `?njref=` querystring)         |
| referralCount        | integer      | Number of users referred                                                    |
| orders               | array        | List of purchase orders with entry details                                  |
| orders[].orderId     | string       | The Shopify order ID                                                        |
| orders[].orderPoints | string       | Points earned from this order                                               |
| orders[].validation  | string (JSON)| Detailed entry calculation breakdown                                        |

##### Validation JSON Fields (inside orders[].validation)

| Field                    | Type    | Description                                                  |
|--------------------------|---------|--------------------------------------------------------------|
| OrderId                  | string  | The order number                                             |
| OrderSource              | string  | Where the order came from (e.g. `"shopify"`)                 |
| OrderSourceResult        | object  | `{ Description, IsValid }` — source validation result        |
| OrderPoints              | integer | Base points from the order total rule                        |
| OrderPointsDescription   | string  | Human-readable explanation of order points calculation        |
| BundleRulePoints         | integer | Points from matched bundle rules                             |
| BundleRuleResults        | array   | List of bundle rule match descriptions                       |
| PurchaseRulePoints       | integer | Points from product-specific purchase rules                  |
| PurchaseRuleResults      | array   | List of purchase rule match descriptions                     |
| TotalPoints              | integer | Final total after all multipliers                            |
| PurchasePeriodResult     | object  | `{ Description, Multiplier }` or `null` — time booster info  |
| AovBoosterResult         | object  | `{ Description, Multiplier }` or `null` — AOV booster info   |
| OrderDateRangeResult     | object  | `{ Description, IsValid }` — date range validation           |
| MinimumOrderValueResult  | object  | `{ Description, IsValid }` — minimum order validation        |
| AttributionMethod        | string  | `"subtotal"` or `"total"`                                    |
| GiftCardAmount           | decimal | Gift card amount used (null if none)                         |
| GiftCardInfo             | string  | Gift card details (null if none)                             |

#### Error Responses

| Status                 | Description                                              |
|------------------------|----------------------------------------------------------|
| PayloadMissing         | Request body is empty or malformed                       |
| TooManyRequests        | Rate limit exceeded (15 requests/min per IP)             |
| MissingApiKey          | The `apiKey` field is missing                            |
| InvalidApiKey          | The API key does not match any subscription              |
| OriginNotAllowed       | The request origin is not in your Allowed Origins list   |
| MissingGivewayId       | The `giveawayId` field is missing                        |
| EmailMissing           | The `email` field is missing                             |
| GiveawayOrUserNotFound | No user found with this email in the specified giveaway  |

#### JavaScript Example

```javascript
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));
```

---

### POST /api/client/calculateEntries

Calculate entries a single product would earn based on purchase rules, bundle rules, time multipliers, and AOV boosters.

#### Request Body

| Parameter | Type    | Required | Description                               |
|-----------|---------|----------|-------------------------------------------|
| giveawayId| string  | Yes      | The giveaway ObjectId                     |
| apiKey    | string  | Yes      | Your Public API Key                       |
| productId | string  | Yes      | Shopify product ID                        |
| variantId | string  | Yes      | Shopify variant ID                        |
| quantity  | integer | Yes      | Quantity (must be > 0)                    |
| price     | integer | Yes      | Unit price in cents (e.g. $50.00 = 5000)  |

#### Success Response

```json
{
  "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
  }
}
```

#### Response Fields

| Field                    | Type        | Description                                                              |
|--------------------------|-------------|--------------------------------------------------------------------------|
| totalEntries             | integer     | Total product entries calculated                                         |
| ruleType                 | string/null | Product rule type: `"quantity"` or `"amount"`, or `null` if no rule      |
| ruleEntries              | integer     | Entries awarded per rule threshold                                       |
| ruleQuantity             | integer     | Quantity threshold (for quantity-based rules)                             |
| ruleAmount               | decimal     | Amount threshold (for amount-based rules)                                |
| multiplier               | integer     | Active time booster multiplier (1 = no boost)                            |
| orderRuleEntries         | integer     | Base entries from order-level rule                                       |
| orderRuleAmount          | decimal     | Order rule amount threshold (0 = fixed mode)                             |
| hasOrderRule             | boolean     | Whether order-level rules apply                                          |
| rewardAmountSpent        | integer     | Reward mode: 0 = fixed per order, 1 = per amount spent, 2 = product rules only |
| isExpired                | boolean     | `true` if the giveaway is not active                                     |
| bonusOrderEntries        | integer     | Bonus entries for orders over a threshold                                |
| bonusOrderEntriesOver    | decimal     | Order value threshold for bonus entries                                  |
| aovMultiplier            | integer     | AOV booster multiplier (if product is a booster)                         |
| isAovBooster             | boolean     | `true` if this product is an AOV booster                                 |
| aovExcludeFromOrderTotal | boolean     | `true` if this booster's value is excluded from order total calculations |

#### Error Responses

| Status              | Description                                                                         |
|---------------------|-------------------------------------------------------------------------------------|
| PayloadMissing      | Request body is empty or malformed                                                  |
| TooManyRequests     | Rate limit exceeded                                                                 |
| MissingApiKey       | The `apiKey` field is missing                                                       |
| InvalidApiKey       | The API key does not match any subscription                                         |
| OriginNotAllowed    | The request origin is not in your Allowed Origins list                              |
| MissingGiveawayId   | The `giveawayId` field is missing                                                   |
| MissingProductId    | The `productId` field is missing                                                    |
| MissingVariantId    | The `variantId` field is missing                                                    |
| InvalidQuantity     | Quantity must be greater than 0                                                     |
| InvalidPrice        | Price cannot be negative                                                            |
| GiveawayNotFound    | No giveaway found with the given ID                                                 |
| NoBoostSalesAction  | The giveaway does not have purchase entries enabled                                 |
| InvalidProductId    | The product ID is not a valid number                                                |
| InvalidVariantId    | The variant ID is not a valid number                                                |
| ValidationFailed    | Product did not match any entry rule (response includes `data` with zero entries)   |

#### JavaScript Example

```javascript
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
  })
})
.then(response => response.json())
.then(data => {
  if (data.status === 'Success') {
    console.log('Entries for this product:', data.data.totalEntries);
  }
})
.catch(error => console.error('Request failed:', error));
```

---

### POST /api/client/calculateEntriesBatch

Calculate entries for multiple products in a single request. Ideal for collection/grid pages.

#### Request Body

| Parameter | Type   | Required | Description                  |
|-----------|--------|----------|------------------------------|
| giveawayId| string | Yes      | The giveaway ObjectId        |
| apiKey    | string | Yes      | Your Public API Key          |
| products  | array  | Yes      | Array of product objects      |

##### products[] object

| Parameter | Type    | Required | Description                               |
|-----------|---------|----------|-------------------------------------------|
| productId | string  | Yes      | Shopify product ID                        |
| variantId | string  | Yes      | Shopify variant ID                        |
| quantity  | integer | Yes      | Quantity (must be > 0)                    |
| price     | integer | Yes      | Unit price in cents (e.g. $50.00 = 5000)  |

#### Success Response

```json
{
  "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 (same order as the request). Each result includes all fields from `calculateEntries` plus `productId`, `variantId`, and an individual `status`.

**Per-product status values:** `Success`, `Giveaway-Expired`, `ValidationFailed`, `InvalidProductId`, or `InvalidVariantId`.

#### Error Responses

| Status             | Description                                              |
|--------------------|----------------------------------------------------------|
| PayloadMissing     | Request body is empty or malformed                       |
| TooManyRequests    | Rate limit exceeded                                      |
| MissingApiKey      | The `apiKey` field is missing                            |
| InvalidApiKey      | The API key does not match any subscription              |
| OriginNotAllowed   | The request origin is not in your Allowed Origins list   |
| MissingGiveawayId  | The `giveawayId` field is missing                        |
| MissingProducts    | The `products` array is missing or empty                 |
| MissingProductId   | A product in the array is missing `productId`            |
| MissingVariantId   | A product in the array is missing `variantId`            |
| InvalidQuantity    | A product has quantity <= 0                              |
| InvalidPrice       | A product has negative price                             |
| GiveawayNotFound   | No giveaway found with the given ID                      |
| NoBoostSalesAction | The giveaway does not have purchase entries enabled      |

#### JavaScript Example

```javascript
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(product => {
      console.log('Product ' + product.productId + ': ' + product.totalEntries + ' entries');
    });
  }
})
.catch(error => console.error('Request failed:', error));
```

---

### POST /api/client/calculateOrderEntries

Calculate total entries for an entire cart/order, including order-level points, purchase rules, bundle rules, time multipliers, AOV boosters, and minimum order requirements.

#### Request Body

| Parameter  | Type   | Required | Description            |
|------------|--------|----------|------------------------|
| giveawayId | string | Yes      | The giveaway ObjectId  |
| apiKey     | string | Yes      | Your Public API Key    |
| cartData   | object | Yes      | Cart data object       |

##### cartData object

| Parameter | Type    | Required | Description                                           |
|-----------|---------|----------|-------------------------------------------------------|
| items     | array   | Yes      | Array of cart items                                   |
| subtotal  | integer | Yes      | Cart subtotal in cents (before discounts/shipping/taxes) |
| total     | integer | Yes      | Cart total in cents (after discounts, before shipping/taxes) |
| currency  | string  | No       | Currency code (e.g. `"USD"`)                          |

##### cartData.items[] object

| Parameter | Type    | Required | Description                                                       |
|-----------|---------|----------|-------------------------------------------------------------------|
| productId | string  | Yes      | Shopify product ID                                                |
| variantId | string  | Yes      | Shopify variant ID                                                |
| quantity  | integer | Yes      | Quantity of this item in the cart                                  |
| price     | integer | Yes      | Line total in cents (unit price x quantity, e.g. 2 x $25 = 5000) |

> **Important:** The `price` field in `cartData.items` is the **line total** (unit price x quantity) in cents, not the unit price. This differs from `calculateEntries` where `price` is the unit price.

#### Success Response

```json
{
  "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
  }
}
```

#### Response Fields

| Field                | Type    | Description                                                       |
|----------------------|---------|-------------------------------------------------------------------|
| totalEntries         | integer | Total entries for the entire order (0 if minimum order not reached)|
| orderPoints          | integer | Points from the order-level rule                                  |
| bundleRulePoints     | integer | Points from matched bundle rules                                  |
| purchaseRulePoints   | integer | Points from product-specific purchase rules                       |
| multiplier           | integer | Active time booster multiplier (1 = no boost)                     |
| aovMultiplier        | integer | AOV booster multiplier                                            |
| calculationBase      | decimal | Order value used for calculation (after deducting excluded products)|
| attributionMode      | string  | `"subtotal"` or `"total"` — which order value is used             |
| isExpired            | boolean | `true` if the giveaway is not active                              |
| hasMinimumOrder      | boolean | `true` if a minimum order value is configured                     |
| minimumOrderValue    | decimal | Minimum order value required to earn entries                      |
| minimumOrderReached  | boolean | `true` if the current order meets the minimum                     |

> **Empty cart:** If `cartData` is null or has no items, the API returns `Success` with `totalEntries: 0` rather than an error.

#### Error Responses

| Status             | Description                                              |
|--------------------|----------------------------------------------------------|
| PayloadMissing     | Request body is empty or malformed                       |
| TooManyRequests    | Rate limit exceeded                                      |
| MissingApiKey      | The `apiKey` field is missing                            |
| InvalidApiKey      | The API key does not match any subscription              |
| OriginNotAllowed   | The request origin is not in your Allowed Origins list   |
| MissingGiveawayId  | The `giveawayId` field is missing                        |
| GiveawayNotFound   | No giveaway found with the given ID                      |
| NoBoostSalesAction | The giveaway does not have purchase entries enabled      |

#### JavaScript Example (Shopify Cart)

```javascript
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
      })),
      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);
      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));
```
