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
- ERP Integration: Connect Shoplazza with your ERP system
- Custom Admin Tools: Build internal dashboards and reports
- Warehouse Management: Sync inventory with your warehouse
- Accounting Integration: Export orders to accounting software
- Custom Workflows: Automate specific business processes
- Agency Solutions: Build tools for specific clients
Creating a Private App
Step 1: Access Your Store Admin
- Log in to your Shoplazza admin panel
- Navigate to Settings > Apps and sales channels
- Click Develop apps button
- If prompted, enable app development
Step 2: Create the App
- Click Create an app
- 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:
| Feature | Private App | Public 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 |