Catalog Module
Manage product databases, generate print-ready catalogs with multi-language support, Shopify sync, and professional PDF output.
Base URL: https://app.proign.com/[tenant]/catalog/api
Overview
The Catalog module provides a centralized product database with professional PDF catalog generation:
- Product database with rich metadata, translations, and images
- Professional PDF catalog generation via queue-based rendering
- Multi-language support for global markets
- Real-time Shopify product sync via webhooks
- Category and collection management with hierarchical structure
- Product badges for quality marks, programs, and warnings
- Customizable page templates and content blocks
- Auto-layout engine for rapid catalog assembly
Key Features
Product Database
Centralized product information management with full multilingual support:
- Products - SKU, translations (name, description, features per language), images, pricing, and specifications
- Categories - Hierarchical product categorization with parent-child relationships
- Badges - Quality marks, program icons, and warning labels attached to products
- Compatibility - Cross-reference arrays linking products to compatible models
- Media - Primary image plus additional image gallery, stored in R2
Product Schema
Each product stores the following fields. The translations field holds all localizable content keyed by language code:
{
"id": "a1b2c3d4e5f6...",
"tenant_id": "CANIKUSA",
"sku": "TP9-SFX-001",
"status": "active", // "draft" | "active" | "archived"
"translations": {
"en": {
"name": "TP9 SFx Rival-S",
"description": "Competition-ready 9mm with...",
"features": ["Match-grade barrel", "Fiber optic sights"]
},
"tr": {
"name": "TP9 SFx Rival-S",
"description": "Yarişma tipi 9mm...",
"features": ["Match sınıfı namlu", "Fiber optik nişangah"]
}
},
"primary_image_r2_key": "catalog/CANIKUSA/products/tp9-sfx.jpg",
"image_r2_keys": [
"catalog/CANIKUSA/products/tp9-sfx-angle.jpg",
"catalog/CANIKUSA/products/tp9-sfx-detail.jpg"
],
"category_id": "cat_pistols",
"badge_ids": ["badge_quality", "badge_new"],
"warning_text": "Read manual before use",
"color_hex": "#1a1a1a",
"compatibility": ["TP9 Series", "METE Series"],
"shopify_product_id": "8012345678901",
"shopify_variant_id": "4412345678901",
"shopify_synced_at": "2026-02-20T14:30:00.000Z",
"price_cents": 59999,
"currency": "USD",
"sort_order": 10,
"created_at": "2026-01-15T10:00:00.000Z",
"updated_at": "2026-02-20T14:30:00.000Z"
}PDF Template Customization
Catalogs are assembled from structured pages, each with a type and optional template. The system supports five page types:
Page Types
| Type | Description |
|---|---|
cover | Full-page cover with branding, title, and background image |
toc | Auto-generated table of contents from category pages |
category_divider | Section divider page introducing a product category |
product_grid | Product layout page with assigned products and content blocks |
custom | Free-form page built entirely from content blocks |
Content Blocks
Each page can contain ordered content blocks that define the page layout. Blocks are reorderable and each stores its own content payload:
| Block Type | Usage |
|---|---|
heading | Section titles and subtitles |
paragraph | Rich text content |
image | Product photos, lifestyle images, diagrams |
table | Specification tables, comparison charts |
list | Feature lists, bullet points |
warning_box | Safety notices, regulatory callouts |
qr_code | QR codes linking to digital resources |
spacer | Vertical spacing between blocks |
divider | Horizontal rule separators |
Custom Branding
Each catalog stores a branding object and a cover_config object for full visual customization:
- Branding - Logo, primary color, accent color, footer text, fonts
- Cover Config - Background image, title placement, subtitle, year badge
- Page Format - A4, Letter, or A5 in portrait or landscape orientation
- Custom Dimensions - Override standard formats with exact width/height (mm)
- Document Type - Catalog, manual, packing list, or card layout
Page Templates
Reusable templates define default layouts for page types. Templates store their layout as a JSON configuration and can be marked as the default for their type:
// Example template layout (product_grid)
{
"name": "2-Column Product Grid",
"page_type": "product_grid",
"layout_json": {
"columns": 2,
"showPrice": true,
"showSku": true,
"showDescription": true,
"imagePosition": "top",
"badgePlacement": "top-right"
},
"is_default": 1
}Auto-Layout
The auto-layout engine generates catalog pages from a list of category IDs. It creates category dividers and product grid pages automatically, including a table of contents if enabled on the catalog:
POST /api/[tenant]/catalog/catalogs/[id]/auto-layout
Content-Type: application/json
{
"categoryIds": ["cat_pistols", "cat_accessories", "cat_optics"]
}Shopify Sync
The catalog module integrates with Shopify via HMAC-validated webhooks. Product data flows from Shopify into the catalog in real time:
Sync Flow
- Connect your Shopify store via Settings (store domain, access token, client secret)
- Register webhooks for
products/create,products/update, andproducts/delete - Shopify sends HMAC-signed payloads to
/api/[tenant]/catalog/shopify/webhook - The module validates the HMAC signature, identifies the tenant from the shop domain header, and upserts the product
- Any catalogs containing the affected product are automatically queued for PDF regeneration
Product Matching
Products are matched using the Shopify product ID. On create or update:
- If a product with the same
shopify_product_idexists, it is updated in place (upsert) - If no match exists, a new product record is created with status mapped from Shopify (
activestays active, everything else becomesdraft) - SKU is pulled from the first variant; price is converted to cents
- Product title and HTML-stripped description are stored in the
entranslation - Image URLs from Shopify are stored in
image_r2_keysuntil they are downloaded to R2
Conflict Resolution
The webhook-based sync follows last-write-wins semantics:
- Shopify is treated as the source of truth for synced fields (title, description, price, SKU, status)
- Local-only fields (badges, compatibility, warning text, category assignment, color) are preserved during sync
- The
shopify_synced_attimestamp records when the last sync occurred - On product delete, the local product is removed and affected catalogs are regenerated
Security
Shopify credentials (access token and client secret) are encrypted at rest using AES encryption with tenant-scoped keys. HMAC validation ensures webhook authenticity. The tenant is identified from the x-shopify-shop-domain header, preventing cross-tenant data leaks.
Multi-Language Support
Translations are stored directly on each product in a JSON map keyed by ISO language code. Each language entry contains the localized name, description, and feature list:
"translations": {
"en": {
"name": "Elite Combat Executive",
"description": "Premium carry pistol with...",
"features": ["SAI barrel", "Tungsten guide rod"]
},
"de": {
"name": "Elite Combat Executive",
"description": "Premium-Tragepistole mit...",
"features": ["SAI-Lauf", "Wolfram-Führungsstange"]
},
"fr": {
"name": "Elite Combat Executive",
"description": "Pistolet de port premium avec...",
"features": ["Canon SAI", "Tige de guidage en tungstène"]
}
}Localized Catalogs
Each catalog has a language and optional region field. When generating a PDF, the system pulls translations matching the catalog language. To produce the same catalog in multiple languages:
- Create the master catalog in your default language with all pages and products
- Use the Duplicate Catalog endpoint with a different
languageandregion - The duplicate copies all pages, products, and blocks but renders using the target language translations
- Each localized catalog can be independently customized after duplication
POST /api/[tenant]/catalog/catalogs/[id]/duplicate
Content-Type: application/json
{
"name": "2026 Produktkatalog",
"slug": "2026-catalog-de",
"language": "de",
"region": "DACH"
}API Endpoints
Products
GET /api/[tenant]/catalog/products # List products (filterable)
GET /api/[tenant]/catalog/products/[id] # Get product details
POST /api/[tenant]/catalog/products # Create product
PATCH /api/[tenant]/catalog/products/[id] # Update product
Query params: categoryId, status (draft|active|archived), search, limit (1-100, default 50), offset (default 0).
Categories
GET /api/[tenant]/catalog/categories # List categories
POST /api/[tenant]/catalog/categories # Create category
PATCH /api/[tenant]/catalog/categories/[id] # Update category
DELETE /api/[tenant]/catalog/categories/[id] # Delete category
Badges
GET /api/[tenant]/catalog/badges # List badges
POST /api/[tenant]/catalog/badges # Create badge
PATCH /api/[tenant]/catalog/badges/[id] # Update badge
DELETE /api/[tenant]/catalog/badges/[id] # Delete badge
Catalogs
GET /api/[tenant]/catalog/catalogs # List catalogs
POST /api/[tenant]/catalog/catalogs # Create catalog
GET /api/[tenant]/catalog/catalogs/[id] # Get catalog details
PATCH /api/[tenant]/catalog/catalogs/[id] # Update catalog
DELETE /api/[tenant]/catalog/catalogs/[id] # Delete catalog
POST /api/[tenant]/catalog/catalogs/[id]/duplicate # Duplicate catalog
POST /api/[tenant]/catalog/catalogs/[id]/auto-layout # Auto-generate pages
Catalog Pages
GET /api/[tenant]/catalog/catalogs/[id]/pages # List pages
POST /api/[tenant]/catalog/catalogs/[id]/pages # Create page
GET /api/[tenant]/catalog/catalogs/[id]/pages/[pageId] # Get page
PATCH /api/[tenant]/catalog/catalogs/[id]/pages/[pageId] # Update page
DELETE /api/[tenant]/catalog/catalogs/[id]/pages/[pageId] # Delete page
POST /api/[tenant]/catalog/catalogs/[id]/pages/reorder # Reorder pages
Page Products
GET /api/.../pages/[pageId]/products # List page products
POST /api/.../pages/[pageId]/products # Add product to page
PATCH /api/.../pages/[pageId]/products/[ppId] # Update page product
DELETE /api/.../pages/[pageId]/products/[ppId] # Remove product from page
POST /api/.../pages/[pageId]/products/reorder # Reorder products
Content Blocks
GET /api/.../pages/[pageId]/blocks # List blocks
POST /api/.../pages/[pageId]/blocks # Create block
PATCH /api/.../pages/[pageId]/blocks/[blockId] # Update block
DELETE /api/.../pages/[pageId]/blocks/[blockId] # Delete block
POST /api/.../pages/[pageId]/blocks/reorder # Reorder blocks
Templates
GET /api/[tenant]/catalog/templates # List templates
POST /api/[tenant]/catalog/templates # Create template
PATCH /api/[tenant]/catalog/templates/[id] # Update template
DELETE /api/[tenant]/catalog/templates/[id] # Delete template
PDF Generation
POST /api/[tenant]/catalog/generate # Queue catalog PDF generation
GET /api/[tenant]/catalog/generate/[jobId] # Check generation status
Settings and Webhooks
GET /api/[tenant]/catalog/settings # Get catalog settings
PUT /api/[tenant]/catalog/settings # Update settings
POST /api/[tenant]/catalog/shopify/webhook # Shopify webhook receiver
Request Examples
Create Product
POST /api/[tenant]/catalog/products
Content-Type: application/json
Authorization: Bearer <jwt>
{
"sku": "TP9-METE-SFT",
"status": "active",
"translations": {
"en": {
"name": "METE SFT",
"description": "Full-size tactical pistol...",
"features": ["18+1 capacity", "Optic-ready slide"]
}
},
"category_id": "cat_pistols",
"badge_ids": ["badge_new"],
"price_cents": 47999,
"currency": "USD",
"compatibility": ["METE Series"]
}Response (201):
{
"data": {
"id": "a1b2c3d4e5f6...",
"tenant_id": "CANIKUSA",
"sku": "TP9-METE-SFT",
"status": "active",
"translations": { "en": { ... } },
"category_id": "cat_pistols",
"badge_ids": ["badge_new"],
"price_cents": 47999,
"currency": "USD",
"created_at": "2026-02-21T10:00:00.000Z",
"updated_at": "2026-02-21T10:00:00.000Z"
}
}Create Catalog
POST /api/[tenant]/catalog/catalogs
Content-Type: application/json
Authorization: Bearer <jwt>
{
"name": "2026 Product Catalog",
"slug": "2026-catalog-en",
"language": "en",
"region": "US",
"page_format": "letter",
"orientation": "portrait",
"document_type": "catalog",
"toc_enabled": 1,
"branding": {
"logo_r2_key": "catalog/CANIKUSA/logo.png",
"primary_color": "#1a1a1a",
"accent_color": "#c8102e",
"footer_text": "CANIK USA 2026"
},
"cover_config": {
"background_image": "catalog/CANIKUSA/cover-bg.jpg",
"title": "2026 PRODUCT CATALOG",
"subtitle": "Innovation Meets Precision"
}
}Add Content Block to Page
POST /api/[tenant]/catalog/catalogs/[id]/pages/[pageId]/blocks
Content-Type: application/json
Authorization: Bearer <jwt>
{
"block_type": "warning_box",
"sort_order": 3,
"content": {
"text": "Always follow firearm safety rules.",
"icon": "warning",
"style": "danger"
}
}Generate PDF
POST /api/[tenant]/catalog/generate
Content-Type: application/json
Authorization: Bearer <jwt>
{
"catalogId": "abc123def456"
}Response (202): The job is queued for processing. Poll the status endpoint to track progress:
{
"data": {
"jobId": "output_xyz789",
"status": "queued",
"version_number": 3,
"trigger_source": "manual"
}
}
// Poll: GET /api/[tenant]/catalog/generate/output_xyz789
// -> { "data": { "status": "completed", "r2_key": "...", "page_count": 48 } }Configuration
Configure the catalog module via PUT /api/[tenant]/catalog/settings:
- Shopify Store Domain - The
mystore.myshopify.comdomain for webhook matching - Shopify Access Token - API access token for product sync (encrypted at rest)
- Shopify Client Secret - Used for HMAC webhook validation (encrypted at rest)
- Default Language - Fallback language code for new catalogs (e.g.,
en) - Default Region - Default market region for new catalogs (e.g.,
US)
PUT /api/[tenant]/catalog/settings
Content-Type: application/json
Authorization: Bearer <jwt>
{
"shopify_store_domain": "canikusa.myshopify.com",
"shopify_access_token": "shpat_xxxxxxxxxxxx",
"shopify_client_secret": "shpss_xxxxxxxxxxxx",
"default_language": "en",
"default_region": "US"
}Sensitive fields are returned as **** in GET responses. Shopify credentials are AES-encrypted with tenant-scoped keys before storage.