Skip to main content

Quick Construction of Private Apps

Private apps are perfect for custom integrations and internal tools. Learn how to build one quickly.

What is a Private App?

A private app is an application that:

  • 🔒 Is not listed in the public App Store
  • 🎯 Can be installed only by specific merchants
  • ⚡ Doesn't require public review
  • 🔑 Has full API access

Use Cases

Common Scenarios

  1. ERP Integration: Connect Shoplazza with your ERP system
  2. Custom Admin Tools: Build internal dashboards and reports
  3. Warehouse Management: Sync inventory with your warehouse
  4. Accounting Integration: Export orders to accounting software
  5. Custom Workflows: Automate specific business processes
  6. Agency Solutions: Build tools for specific clients

Creating a Private App

Step 1: Access Your Store Admin

  1. Log in to your Shoplazza admin panel
  2. Navigate to Settings > Apps and sales channels
  3. Click Develop apps button
  4. If prompted, enable app development

Step 2: Create the App

  1. Click Create an app
  2. Enter app details:
    • App name: Descriptive internal name
    • App developer: Your name or company
    • Email: Technical contact email

Step 3: Configure API Permissions

Select the API scopes your app needs:

Product Management

  • read_products - View products
  • write_products - Create/update products

Order Management

  • read_orders - View orders
  • write_orders - Create/update orders

Customer Management

  • read_customers - View customers
  • write_customers - Create/update customers

Inventory Management

  • read_inventory - View inventory levels
  • write_inventory - Update inventory

Step 4: Get Your API Credentials

After creating your app, you'll receive:

{
"api_key": "shpka_1234567890abcdef",
"api_secret": "shpss_1234567890abcdef",
"access_token": "shpat_1234567890abcdef"
}
Security Warning

⚠️ Important: Store these credentials securely!

  • Never commit them to version control
  • Use environment variables
  • Never expose them in client-side code
  • Rotate regularly if compromised

Making API Calls

Setting Up Your Environment

# .env file
SHOP_DOMAIN=your-shop.shoplazza.com
ACCESS_TOKEN=shpat_1234567890abcdef
API_VERSION=2024-01

REST API Examples

Fetching Products

const axios = require('axios');
require('dotenv').config();

const shopDomain = process.env.SHOP_DOMAIN;
const accessToken = process.env.ACCESS_TOKEN;
const apiVersion = process.env.API_VERSION;

async function fetchProducts() {
try {
const response = await axios.get(
`https://${shopDomain}/admin/api/${apiVersion}/products.json`,
{
headers: {
'X-Shoplazza-Access-Token': accessToken
},
params: {
limit: 50,
fields: 'id,title,variants'
}
}
);

console.log(`Found ${response.data.products.length} products`);
return response.data.products;
} catch (error) {
console.error('Error fetching products:', error.response?.data || error.message);
throw error;
}
}

Creating a Product

async function createProduct(productData) {
const product = {
title: productData.title,
body_html: productData.description,
vendor: productData.vendor,
product_type: productData.type,
tags: productData.tags,
variants: [{
price: productData.price,
sku: productData.sku,
inventory_quantity: productData.quantity,
weight: productData.weight,
weight_unit: "kg"
}],
images: productData.images?.map(url => ({ src: url }))
};

const response = await axios.post(
`https://${shopDomain}/admin/api/${apiVersion}/products.json`,
{ product },
{
headers: {
'X-Shoplazza-Access-Token': accessToken,
'Content-Type': 'application/json'
}
}
);

return response.data.product;
}

Processing Orders

async function getUnfulfilledOrders() {
const response = await axios.get(
`https://${shopDomain}/admin/api/${apiVersion}/orders.json`,
{
headers: {
'X-Shoplazza-Access-Token': accessToken
},
params: {
status: 'open',
fulfillment_status: 'unfulfilled'
}
}
);

return response.data.orders;
}

async function fulfillOrder(orderId, trackingInfo) {
const fulfillment = {
tracking_number: trackingInfo.trackingNumber,
tracking_company: trackingInfo.carrier,
tracking_url: trackingInfo.trackingUrl,
notify_customer: true
};

const response = await axios.post(
`https://${shopDomain}/admin/api/${apiVersion}/orders/${orderId}/fulfillments.json`,
{ fulfillment },
{
headers: {
'X-Shoplazza-Access-Token': accessToken,
'Content-Type': 'application/json'
}
}
);

return response.data.fulfillment;
}

GraphQL API Examples

async function fetchProductsGraphQL() {
const query = `
query getProducts($first: Int!) {
products(first: $first) {
edges {
node {
id
title
description
variants(first: 5) {
edges {
node {
id
title
price
sku
inventoryQuantity
}
}
}
}
}
pageInfo {
hasNextPage
hasPreviousPage
}
}
}
`;

const response = await axios.post(
`https://${shopDomain}/admin/api/${apiVersion}/graphql.json`,
{
query,
variables: { first: 10 }
},
{
headers: {
'X-Shoplazza-Access-Token': accessToken,
'Content-Type': 'application/json'
}
}
);

return response.data.data.products;
}

Common Integration Patterns

1. Inventory Sync

Sync inventory between Shoplazza and your warehouse system:

class InventorySync {
async syncInventory(warehouseData) {
for (const item of warehouseData) {
await this.updateInventoryLevel(
item.sku,
item.available_quantity
);
}
}

async updateInventoryLevel(sku, quantity) {
// Find product by SKU
const product = await this.findProductBySKU(sku);

if (!product) {
console.warn(`Product not found for SKU: ${sku}`);
return;
}

// Update inventory
await axios.post(
`https://${shopDomain}/admin/api/${apiVersion}/inventory_levels/set.json`,
{
inventory_item_id: product.inventory_item_id,
location_id: product.location_id,
available: quantity
},
{
headers: {
'X-Shoplazza-Access-Token': accessToken
}
}
);
}
}

2. Order Export to ERP

Export orders to your ERP system:

class OrderExporter {
async exportNewOrders() {
const orders = await this.getNewOrders();

for (const order of orders) {
const erpOrder = this.transformToERPFormat(order);
await this.sendToERP(erpOrder);
await this.markAsExported(order.id);
}
}

transformToERPFormat(shopOrder) {
return {
order_number: shopOrder.name,
customer: {
name: shopOrder.customer.name,
email: shopOrder.customer.email
},
items: shopOrder.line_items.map(item => ({
sku: item.sku,
quantity: item.quantity,
price: item.price
})),
total: shopOrder.total_price,
shipping_address: shopOrder.shipping_address
};
}
}

3. Automated Product Import

Import products from external source:

async function importProducts(csvFilePath) {
const products = await parseCsvFile(csvFilePath);

for (const productData of products) {
try {
await createProduct({
title: productData.name,
description: productData.description,
vendor: productData.brand,
type: productData.category,
price: productData.price,
sku: productData.sku,
quantity: productData.stock,
tags: productData.tags?.split(',')
});

console.log(`✅ Imported: ${productData.name}`);
} catch (error) {
console.error(`❌ Failed to import ${productData.name}:`, error.message);
}
}
}

Error Handling & Retry Logic

async function apiCallWithRetry(apiFunction, maxRetries = 3) {
let lastError;

for (let i = 0; i < maxRetries; i++) {
try {
return await apiFunction();
} catch (error) {
lastError = error;

// Handle rate limiting
if (error.response?.status === 429) {
const retryAfter = error.response.headers['retry-after'] || 2;
console.log(`Rate limited. Waiting ${retryAfter}s...`);
await sleep(retryAfter * 1000);
continue;
}

// Handle temporary server errors
if (error.response?.status >= 500) {
const backoff = Math.pow(2, i) * 1000;
console.log(`Server error. Retrying in ${backoff}ms...`);
await sleep(backoff);
continue;
}

// Don't retry client errors
throw error;
}
}

throw lastError;
}

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

Security Best Practices

1. Environment Variables

// ✅ Good - Use environment variables
const config = {
shopDomain: process.env.SHOP_DOMAIN,
accessToken: process.env.ACCESS_TOKEN
};

// ❌ Bad - Hardcoded credentials
const config = {
shopDomain: 'myshop.shoplazza.com',
accessToken: 'shpat_123456'
};

2. Secure Token Storage

// Use encrypted storage for tokens
const crypto = require('crypto');

class SecureStorage {
constructor(encryptionKey) {
this.algorithm = 'aes-256-gcm';
this.key = Buffer.from(encryptionKey, 'hex');
}

encrypt(text) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(this.algorithm, this.key, iv);

let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');

const authTag = cipher.getAuthTag();

return {
iv: iv.toString('hex'),
encryptedData: encrypted,
authTag: authTag.toString('hex')
};
}

decrypt(encrypted) {
const decipher = crypto.createDecipheriv(
this.algorithm,
this.key,
Buffer.from(encrypted.iv, 'hex')
);

decipher.setAuthTag(Buffer.from(encrypted.authTag, 'hex'));

let decrypted = decipher.update(encrypted.encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');

return decrypted;
}
}

3. Logging and Monitoring

const winston = require('winston');

const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});

async function monitoredApiCall(operation, ...args) {
const startTime = Date.now();

try {
const result = await operation(...args);
logger.info({
operation: operation.name,
duration: Date.now() - startTime,
status: 'success'
});
return result;
} catch (error) {
logger.error({
operation: operation.name,
duration: Date.now() - startTime,
error: error.message,
status: 'error'
});
throw error;
}
}

Testing Your Integration

Unit Tests Example

const { expect } = require('chai');
const nock = require('nock');

describe('Product API', () => {
it('should fetch products successfully', async () => {
// Mock API response
nock(`https://${shopDomain}`)
.get(`/admin/api/${apiVersion}/products.json`)
.reply(200, {
products: [
{ id: 1, title: 'Test Product' }
]
});

const products = await fetchProducts();

expect(products).to.have.lengthOf(1);
expect(products[0].title).to.equal('Test Product');
});
});

Limitations

Private apps have some limitations compared to public apps:

FeaturePrivate AppPublic App
Embedded UI❌ No✅ Yes
OAuth Flow❌ No✅ Yes
App Store Listing❌ No✅ Yes
Multi-merchant⚠️ Manual✅ Automatic
Webhooks✅ Yes✅ Yes
API Access✅ Full✅ Full

Next Steps