REST API v2
Overview

REST API v2

Overview

The Joy Loyalty Program REST API v2 provides comprehensive access to loyalty program functionality including customer management, point transactions, rewards, VIP tiers, and referral systems. This RESTful API follows consistent patterns and includes advanced features like cursor-based pagination, comprehensive error handling, and automatic data filtering.

API Base URLs

API endpoints at: https://dev-api.joy.so

Plan Requirements

⚠️

REST API access is included in the Pro plan and higher. Shops on the Free/Starter plan receive an HTTP 402 error with plan_upgrade_required. See Rate Limiting for the limits on each plan.

Authentication

The API uses header-based authentication with two required headers for all requests:

X-Joy-Loyalty-App-Key: your_app_key_here
X-Joy-Loyalty-Secret-Key: your_secret_key_here

You can retrieve these credentials from your Joy Loyalty app settings page in the Shopify admin.

Authentication Example

curl -X GET "https://dev-api.joy.so/rest_api/v2/customers" \
  -H "X-Joy-Loyalty-App-Key: your_app_key" \
  -H "X-Joy-Loyalty-Secret-Key: your_secret_key" \
  -H "Content-Type: application/json"

Response Format

All API responses follow a consistent envelope structure:

Success Response

{
  "success": true,
  "data": {}, // or []
  "meta": {
    "count": 25, // for list responses
    "pagination": { // for paginated responses
      "hasNext": true,
      "hasPre": false,
      "total": 1250, // when hasCount=true
      "totalPage": 63 // when hasCount=true
    }
  },
  "timestamp": "2023-07-28T07:27:54.123Z",
  "message": "Operation completed successfully" // optional
}

Error Response

{
  "success": false,
  "error": {
    "message": "Resource not found",
    "code": "NOT_FOUND",
    "statusCode": 404,
    "details": {} // optional
  },
  "timestamp": "2023-07-28T07:27:54.123Z"
}

Data Filtering

API responses only include fields with actual data. Fields with null or undefined values are automatically filtered out to provide cleaner responses for customer-facing applications.

Pagination

The API uses cursor-based pagination for efficient navigation through large datasets:

Parameters

  • before: Cursor/document ID to paginate before (previous page)
  • after: Cursor/document ID to paginate after (next page)
  • limit: Page size (endpoint-specific defaults, max: 1000)
  • hasCount: Include total counts (may increase response time)

Default Limits

  • Customers: 20 items per page
  • Activities: 10 items per page
  • Rewards: 10 items per page

Navigation

Use the item ID from responses:

  • First item with before parameter for previous page
  • Last item with after parameter for next page

Example

# First page
curl "https://dev-api.joy.so/rest_api/v2/customers?limit=20&hasCount=true"
 
# Next page (using last item's ID from previous response)
curl "https://dev-api.joy.so/rest_api/v2/customers?after=abc123&limit=20"
 
# Previous page (using first item's ID)
curl "https://dev-api.joy.so/rest_api/v2/customers?before=abc123&limit=20"

Common Query Parameters

Most list endpoints support these filtering and sorting parameters:

Date Filtering

  • created_at_min: Filter records created after this date
  • created_at_max: Filter records created before this date
  • updated_at_min: Filter records updated after this date
  • updated_at_max: Filter records updated before this date

Sorting

  • order: Sort order (varies by endpoint)
    • Common values: createdAt_desc, createdAt_asc, updatedAt_desc, updatedAt_asc

Example with Filters

curl "https://dev-api.joy.so/rest_api/v2/customers?\
created_at_min=2023-01-01T00:00:00Z&\
created_at_max=2023-12-31T23:59:59Z&\
order=createdAt_desc&\
limit=50"

Error Handling

HTTP Status Codes

CodeDescriptionCommon Scenarios
200SuccessRequest completed successfully
400Bad RequestInvalid parameters or request data
401UnauthorizedMissing or invalid authentication
402Payment RequiredPlan upgrade required (Free/Starter plan)
403ForbiddenRead-only key (or private bot key) used for a non-GET request
404Not FoundResource doesn't exist
429Too Many RequestsRate limit or daily quota exceeded
500Internal Server ErrorUnexpected server error

Common Error Codes

Error CodeDescriptionResolution
CUSTOMER_NOT_FOUNDCustomer doesn't existVerify customer ID
PROGRAM_NOT_FOUNDProgram doesn't existCheck program ID
TIER_NOT_FOUNDTier doesn't existVerify tier ID
plan_upgrade_requiredAPI not included in the plan (HTTP 402, in the error field)Upgrade to Pro or higher
SHOP_UNINSTALLEDApp not installed on shop (returned with HTTP 200; check success/code, not only status)Reinstall the app

Error Response Example

{
  "success": false,
  "error": {
    "message": "Customer not found",
    "code": "CUSTOMER_NOT_FOUND",
    "statusCode": 404
  },
  "timestamp": "2023-07-28T07:27:54.123Z"
}

Plan and rate-limit errors (HTTP 402 and 429) use a flat body, with the code in error:

{
  "success": false,
  "error": "daily_quota_exceeded",
  "message": "You've reached your daily API limit (200 requests/day) on the pro_3_2025 plan. Your limit resets at midnight UTC, or upgrade your plan for a higher limit.",
  "limit": 200,
  "used": 201,
  "resetsAt": "2026-10-24T00:00:00.000Z",
  "upgrade_url": "https://admin.shopify.com/store/<store>/apps/<app>/embed/subscription"
}

The error value is plan_upgrade_required for plan access, daily_quota_exceeded for a daily quota, or rate_limit_exceeded for a burst or ceiling limit. The plan_upgrade_required body includes message and upgrade_url; rate_limit_exceeded includes message, limit, and periodSeconds.

Rate Limiting

The API implements rate limiting per shop, based on the shop's Joy plan:

PlanBurst limitDaily quota
Free/StarterNo API access (402 plan_upgrade_required)—
Pro30 requests / 10 seconds200 requests / day
Advanced60 requests / 10 seconds2,000 requests / day
UltimateNo plan limitNo plan limit

On top of the plan limits, every Secret Key is subject to a ceiling of 120 requests / 10 seconds, checked before authentication. This ceiling also applies to Ultimate.

Calls made through the Joy MCP server use the same REST API and share the shop's quota.

  • Daily quota resets at 00:00 UTC.
  • Headers included in response (plans with a daily quota):
    • X-RateLimit-Limit: Daily quota
    • X-RateLimit-Remaining: Requests remaining today
    • X-RateLimit-Reset: Time when the daily quota resets (Unix timestamp, in seconds)

When rate limited, you'll receive a 429 Too Many Requests response with a Retry-After header:

errorCauseRetry-After
daily_quota_exceededDaily quota used upSeconds until next UTC midnight
rate_limit_exceededBurst limit or 120/10s ceiling exceeded10

Best Practices

1. Efficient Pagination

// Use hasCount sparingly to avoid performance impact
const customers = await fetch('/rest_api/v2/customers?hasCount=false');
 
// Navigate using document IDs, not page numbers
const nextPage = await fetch(`/rest_api/v2/customers?after=${lastCustomerId}`);

2. Date Range Queries

// Use proper ISO 8601 format for dates
const recentCustomers = await fetch(
  '/rest_api/v2/customers?created_at_min=2023-07-01T00:00:00Z'
);

3. Error Handling

async function handleApiCall(url) {
  const response = await fetch(url);
  const data = await response.json();
  
  if (!data.success) {
    throw new Error(`${data.error.code}: ${data.error.message}`);
  }
  
  return data.data;
}

4. Authentication Security

  • Store credentials securely (environment variables)
  • Never expose credentials in client-side code
  • Rotate credentials regularly
  • Monitor for unauthorized usage

5. Batch Operations

// Instead of multiple single requests
customers.forEach(id => updateCustomer(id)); // ❌ Inefficient
 
// Use bulk operations when available
await updateMultipleCustomers(customerIds); // ✅ Efficient

Data Types and Formats

Date Formats

  • ISO 8601: 2023-07-28T07:27:54.123Z (API responses)
  • Date Only: YYYY-MM-DD for date fields
  • Birthday: MM/DD format for birthday fields

Customer Types

  • member: Active loyalty program member
  • guest: Guest customer (not joined program)
  • left: Former member who left program

Activity Types

  • earn_point: Points earned
  • redeem_point: Points spent/redeemed
  • adjust_point: Points adjusted by admin

Activity Sources

  • admin: Admin panel action
  • user: Customer action
  • rest_api: API action
  • webhook: Webhook trigger

Program Types

  • earning: Point earning program
  • spending: Point spending/redemption program
  • tier_spending: Tier-specific spending program
  • tier: Tier configuration program

Reward Status

  • active: Available for use
  • used: Already redeemed
  • expired: Past expiration date

Security Considerations

1. Data Privacy

  • Customer emails and personal data are included in responses
  • Ensure compliance with privacy regulations (GDPR, CCPA)
  • Implement proper access controls in your application

2. API Key Security

  • Use HTTPS only for all API requests
  • Store API keys securely (never in version control)
  • Implement key rotation policies
  • Monitor for suspicious activity

3. Input Validation

  • Validate all input data on your end
  • Use proper data types for API calls
  • Sanitize user input before sending to API

4. Error Information

  • Error responses may contain sensitive information
  • Log errors securely without exposing to end users
  • Implement proper error boundaries

Integration Examples

JavaScript/Node.js

Please use our npm package (opens in a new tab) joy-api-node for the best developer experience. For example:

 
import JoyApi from 'joy-api-node';
 
const joy = new JoyApi({
  appKey: 'your-app-key',
  secretKey: 'your-secret-key',
  baseUrl: 'https://dev-api.joy.so', // optional, defaults to production
  timeout: 30000, // optional, request timeout in ms
  maxRetries: 3 // optional, number of retries for failed requests
});
 
// Get shop information
const shop = await joy.shop.whoami();
console.log(shop.data);

Python

import requests
from typing import Dict, Optional
 
class JoyApiClient:
    def __init__(self, app_key: str, secret_key: str, base_url: str = 'https://dev-api.joy.so'):
        self.app_key = app_key
        self.secret_key = secret_key
        self.base_url = base_url
        self.session = requests.Session()
        self.session.headers.update({
            'Content-Type': 'application/json',
            'X-Joy-Loyalty-App-Key': app_key,
            'X-Joy-Loyalty-Secret-Key': secret_key
        })
 
    def request(self, endpoint: str, method: str = 'GET', data: Optional[Dict] = None):
        url = f"{self.base_url}{endpoint}"
        response = self.session.request(method, url, json=data)
        
        result = response.json()
        if not result.get('success'):
            error = result.get('error', {})
            raise Exception(f"{error.get('code', 'UNKNOWN_ERROR')}: {error.get('message', 'Unknown error')}")
        
        return result
 
    def get_customers(self, **params):
        query_string = '&'.join([f"{k}={v}" for k, v in params.items()])
        endpoint = f"/rest_api/v2/customers?{query_string}"
        return self.request(endpoint)
 
    def award_points(self, shopify_customer_id: str, points: int, note: str = ''):
        return self.request('/rest_api/v2/transactions/points/award', 'POST', {
            'shopifyCustomerId': shopify_customer_id,
            'point': points,
            'adminNote': note
        })
 
# Usage
client = JoyApiClient('your-app-key', 'your-secret-key')
customers = client.get_customers(limit=20, order='createdAt_desc')

PHP

<?php
class JoyApiClient {
    private $appKey;
    private $secretKey;
    private $baseUrl;
 
    public function __construct($appKey, $secretKey, $baseUrl = 'https://dev-api.joy.so') {
        $this->appKey = $appKey;
        $this->secretKey = $secretKey;
        $this->baseUrl = $baseUrl;
    }
 
    public function request($endpoint, $method = 'GET', $data = null) {
        $url = $this->baseUrl . $endpoint;
        
        $headers = [
            'Content-Type: application/json',
            'X-Joy-Loyalty-App-Key: ' . $this->appKey,
            'X-Joy-Loyalty-Secret-Key: ' . $this->secretKey
        ];
 
        $curl = curl_init();
        curl_setopt_array($curl, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_POSTFIELDS => $data ? json_encode($data) : null
        ]);
 
        $response = curl_exec($curl);
        curl_close($curl);
 
        $result = json_decode($response, true);
        if (!$result['success']) {
            $error = $result['error'] ?? [];
            throw new Exception(($error['code'] ?? 'UNKNOWN_ERROR') . ': ' . ($error['message'] ?? 'Unknown error'));
        }
 
        return $result;
    }
 
    public function getCustomers($params = []) {
        $queryString = http_build_query($params);
        return $this->request("/rest_api/v2/customers?$queryString");
    }
 
    public function awardPoints($shopifyCustomerId, $points, $note = '') {
        return $this->request('/rest_api/v2/transactions/points/award', 'POST', [
            'shopifyCustomerId' => $shopifyCustomerId,
            'point' => $points,
            'adminNote' => $note
        ]);
    }
}
 
// Usage
$client = new JoyApiClient('your-app-key', 'your-secret-key');
$customers = $client->getCustomers(['limit' => 20]);
?>

Changelog

Version 2.0.0 (Current)

  • Initial release of REST API v2
  • Cursor-based pagination implementation
  • Consistent response envelope format
  • Comprehensive error handling
  • Automatic data filtering
  • Plan-based access and rate limits (Pro plan and higher)

Support

For technical support and questions:

  1. Documentation: This guide covers most use cases
  2. API Status: Check API status at status.joy.so
  3. Support Portal: Contact through Joy Loyalty support
  4. Community: Join the Joy Loyalty developer community

Next Steps

  1. Get Started: Set up authentication and make your first API call
  2. Explore Endpoints: Review the detailed endpoint documentation below
  3. Test Integration: Use the provided code examples
  4. Production Deployment: Implement proper error handling and security

This documentation covers the core concepts and best practices for the Joy Loyalty REST API v2. For detailed endpoint specifications, continue reading the endpoint-specific documentation sections.