What We Are Building in Part 4
The local storefront should not require manual copying every time a Printify product, image, price, or variant changes. This chapter builds a controlled synchronization layer between Printify and MySQL.
API Connection
- Personal access token
- Shop ID discovery
- Required request headers
- Private configuration
Product Import
- Printify product IDs
- Blueprint IDs
- Print provider IDs
- Titles and descriptions
Variant Sync
- Color and size
- Cost and retail price
- Enabled status
- Availability
Production Data
- Mockup images
- Print-area placeholders
- Decoration methods
- Provider and shipping data
Product descriptions, collection copy, SEO fields, likes, reviews, and custom merchandising decisions remain under local control.
Contents
- How synchronization works
- Understand the Printify data model
- Create and protect the API token
- Create the private Printify configuration
- Create a reusable API helper
- Retrieve the shop ID
- Retrieve every product page
- Retrieve one complete product
- Add synchronization fields
- Insert or update the local product
- Normalize Printify variants
- Synchronize variants safely
- Synchronize product images
- Load provider and blueprint data
- Store print areas and production data
- Use a database transaction
- Build the complete synchronization script
- Handle missing and disabled variants
- Add logs and synchronization history
- Run synchronization from the admin area
- Part 4 checklist
- Frequently asked questions
How Synchronization Works
Retrieve Printify Products
PHP requests paginated products for the configured Printify shop.
Convert JSON into Local Records
Product, provider, image, option, and variant values are converted into a predictable local structure.
Insert or Update MySQL
Existing records are updated by Printify ID while new products and variants are inserted.
Record the Synchronization
The system stores timestamps, counts, warnings, and errors for review.
Understand the Printify Data Model
Blueprint Data
- Brand and model
- Catalog images
- Available providers
- Possible print areas
Provider Data
- Provider ID and title
- Location
- Available variants
- Decoration methods
Product Data
- Artwork and print areas
- Enabled variants
- Retail prices
- Mockup images
Variant Data
- Variant ID
- Color and size
- Cost and price
- Enabled and available state
Create and Protect the API Token
A personal merchant integration uses a Printify Personal Access Token. The token should include only the scopes required by the synchronization process.
| Scope | Purpose |
|---|---|
shops.read |
Retrieve available Printify shops |
products.read |
Retrieve merchant products and variants |
catalog.read |
Retrieve blueprints and catalog variant data |
print_providers.read |
Retrieve print provider information |
uploads.read |
Retrieve uploaded production image information |
Create the Private Printify Configuration
<?php
/**
* /private/printify.php
*/
declare(strict_types=1);
const PRINTIFY_API_BASE_URL =
'https://api.printify.com/v1';
const PRINTIFY_API_TOKEN =
'REPLACE_WITH_YOUR_PERSONAL_ACCESS_TOKEN';
const PRINTIFY_SHOP_ID =
12345678;
const PRINTIFY_USER_AGENT =
'TheConspiracyShirtCompany/1.0 PHP';
function printifyApiUrl(string $path): string
{
return rtrim(
PRINTIFY_API_BASE_URL,
'/'
) . '/' . ltrim($path, '/');
}
Create a Reusable Printify API Helper
<?php
function printifyRequest(
string $method,
string $path,
?array $body = null
): array {
$url = printifyApiUrl($path);
$curl = curl_init($url);
$headers = [
'Authorization: Bearer ' .
PRINTIFY_API_TOKEN,
'Accept: application/json',
'Content-Type: application/json;charset=utf-8',
'User-Agent: ' .
PRINTIFY_USER_AGENT,
];
$options = [
CURLOPT_CUSTOMREQUEST => strtoupper($method),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 40,
CURLOPT_HTTPHEADER => $headers,
];
if ($body !== null) {
$options[CURLOPT_POSTFIELDS] = json_encode(
$body,
JSON_THROW_ON_ERROR |
JSON_UNESCAPED_SLASHES
);
}
curl_setopt_array($curl, $options);
$responseBody = curl_exec($curl);
$curlError = curl_error($curl);
$statusCode = (int) curl_getinfo(
$curl,
CURLINFO_HTTP_CODE
);
curl_close($curl);
if ($responseBody === false) {
throw new RuntimeException(
'Printify request failed: ' .
$curlError
);
}
$response = json_decode(
$responseBody,
true,
512,
JSON_THROW_ON_ERROR
);
if ($statusCode < 200 || $statusCode >= 300) {
throw new RuntimeException(
sprintf(
'Printify returned HTTP %d: %s',
$statusCode,
json_encode($response)
)
);
}
return $response;
}
Retrieve the Shop ID
<?php
$shops = printifyRequest(
'GET',
'/shops.json'
);
foreach ($shops as $shop) {
printf(
"%d - %s (%s)\n",
(int) $shop['id'],
$shop['title'],
$shop['sales_channel']
);
}
Store the correct numeric shop ID in the private configuration so every product request targets the intended shop.
Retrieve Every Product Page
Printify product listings are paginated. A complete synchronization should continue until the current page reaches the last page.
<?php
function fetchAllPrintifyProducts(
int $shopId,
int $perPage = 50
): array {
$allProducts = [];
$page = 1;
do {
$response = printifyRequest(
'GET',
sprintf(
'/shops/%d/products.json?page=%d&limit=%d',
$shopId,
$page,
$perPage
)
);
$products = $response['data'] ?? [];
foreach ($products as $product) {
$allProducts[] = $product;
}
$currentPage = (int) (
$response['current_page'] ?? $page
);
$lastPage = (int) (
$response['last_page'] ?? $currentPage
);
$page++;
} while ($currentPage < $lastPage);
return $allProducts;
}
Retrieve One Complete Product
The list response is useful for discovery, but a detailed sync should request each product individually.
<?php
function fetchPrintifyProduct(
int $shopId,
string $productId
): array {
return printifyRequest(
'GET',
sprintf(
'/shops/%d/products/%s.json',
$shopId,
rawurlencode($productId)
)
);
}
Add Synchronization Fields
Store enough platform data to identify changes without replacing locally managed content.
ALTER TABLE shirts
ADD COLUMN printify_product_id VARCHAR(100) DEFAULT NULL,
ADD COLUMN printify_blueprint_id INT UNSIGNED DEFAULT NULL,
ADD COLUMN printify_provider_id INT UNSIGNED DEFAULT NULL,
ADD COLUMN printify_status VARCHAR(50) DEFAULT NULL,
ADD COLUMN printify_synced_at DATETIME DEFAULT NULL,
ADD COLUMN printify_updated_at DATETIME DEFAULT NULL,
ADD COLUMN printify_raw_hash CHAR(64) DEFAULT NULL,
ADD UNIQUE KEY uq_printify_product_id (
printify_product_id
),
ADD KEY idx_printify_blueprint_id (
printify_blueprint_id
),
ADD KEY idx_printify_provider_id (
printify_provider_id
);
ALTER TABLE shirt_variants
ADD COLUMN printify_variant_id INT UNSIGNED DEFAULT NULL,
ADD COLUMN printify_cost_cents INT UNSIGNED DEFAULT NULL,
ADD COLUMN printify_price_cents INT UNSIGNED DEFAULT NULL,
ADD COLUMN printify_sku VARCHAR(150) DEFAULT NULL,
ADD COLUMN printify_synced_at DATETIME DEFAULT NULL,
ADD UNIQUE KEY uq_printify_variant (
shirt_id,
printify_variant_id
),
ADD KEY idx_printify_variant_id (
printify_variant_id
);
Insert or Update the Local Product
Match the local record by printify_product_id. Update
operational fields while leaving local descriptions, SEO, likes,
reviews, and collections untouched.
<?php
function upsertPrintifyProduct(
PDO $pdo,
array $product
): int {
$rawHash = hash(
'sha256',
json_encode(
$product,
JSON_THROW_ON_ERROR |
JSON_UNESCAPED_SLASHES
)
);
$stmt = $pdo->prepare("
INSERT INTO shirts (
item_number,
title,
price,
active,
printify_product_id,
printify_blueprint_id,
printify_provider_id,
printify_status,
printify_synced_at,
printify_updated_at,
printify_raw_hash
) VALUES (
:item_number,
:title,
:price,
:active,
:printify_product_id,
:blueprint_id,
:provider_id,
:status,
UTC_TIMESTAMP(),
:printify_updated_at,
:raw_hash
)
ON DUPLICATE KEY UPDATE
title = VALUES(title),
active = VALUES(active),
printify_blueprint_id =
VALUES(printify_blueprint_id),
printify_provider_id =
VALUES(printify_provider_id),
printify_status =
VALUES(printify_status),
printify_synced_at =
UTC_TIMESTAMP(),
printify_updated_at =
VALUES(printify_updated_at),
printify_raw_hash =
VALUES(printify_raw_hash),
id = LAST_INSERT_ID(id)
");
$enabledPrices = [];
foreach ($product['variants'] ?? [] as $variant) {
if (!empty($variant['is_enabled'])) {
$enabledPrices[] = (int) (
$variant['price'] ?? 0
);
}
}
$lowestPriceCents = $enabledPrices
? min($enabledPrices)
: 0;
$stmt->execute([
':item_number' =>
nextLocalItemNumber($pdo),
':title' =>
trim((string) $product['title']),
':price' =>
$lowestPriceCents / 100,
':active' =>
!empty($product['visible']) ? 1 : 0,
':printify_product_id' =>
$product['id'],
':blueprint_id' =>
$product['blueprint_id'] ?? null,
':provider_id' =>
$product['print_provider_id'] ?? null,
':status' =>
$product['is_locked'] ?? false
? 'locked'
: 'available',
':printify_updated_at' =>
normalizePrintifyDate(
$product['updated_at'] ?? null
),
':raw_hash' =>
$rawHash,
]);
return (int) $pdo->lastInsertId();
}
Normalize Printify Variants
Product option names can vary by blueprint. Normalize them into local color and size fields before saving.
<?php
function buildOptionMap(array $product): array
{
$map = [];
foreach ($product['options'] ?? [] as $option) {
$optionName = strtolower(
trim((string) ($option['name'] ?? ''))
);
foreach ($option['values'] ?? [] as $value) {
$valueId = (int) ($value['id'] ?? 0);
$map[$valueId] = [
'type' => $optionName,
'title' => trim(
(string) ($value['title'] ?? '')
),
];
}
}
return $map;
}
function normalizePrintifyVariant(
array $variant,
array $optionMap
): array {
$color = '';
$size = '';
foreach ($variant['options'] ?? [] as $optionId) {
$option = $optionMap[(int) $optionId]
?? null;
if (!$option) {
continue;
}
if ($option['type'] === 'color') {
$color = $option['title'];
}
if ($option['type'] === 'size') {
$size = $option['title'];
}
}
return [
'printify_variant_id' =>
(int) $variant['id'],
'color' => $color,
'size' => $size,
'cost_cents' =>
(int) ($variant['cost'] ?? 0),
'price_cents' =>
(int) ($variant['price'] ?? 0),
'enabled' =>
!empty($variant['is_enabled']),
'available' =>
!empty($variant['is_available']),
'sku' =>
trim((string) ($variant['sku'] ?? '')),
];
}
Synchronize Variants Safely
<?php
function syncPrintifyVariants(
PDO $pdo,
int $shirtId,
array $product
): array {
$optionMap = buildOptionMap($product);
$seenIds = [];
$inserted = 0;
$updated = 0;
$existingStmt = $pdo->prepare("
SELECT id
FROM shirt_variants
WHERE shirt_id = :shirt_id
AND printify_variant_id =
:printify_variant_id
LIMIT 1
");
$upsertStmt = $pdo->prepare("
INSERT INTO shirt_variants (
shirt_id,
color,
size,
price,
enabled,
available,
printify_variant_id,
printify_cost_cents,
printify_price_cents,
printify_sku,
printify_synced_at
) VALUES (
:shirt_id,
:color,
:size,
:price,
:enabled,
:available,
:printify_variant_id,
:cost_cents,
:price_cents,
:sku,
UTC_TIMESTAMP()
)
ON DUPLICATE KEY UPDATE
color = VALUES(color),
size = VALUES(size),
price = VALUES(price),
enabled = VALUES(enabled),
available = VALUES(available),
printify_cost_cents =
VALUES(printify_cost_cents),
printify_price_cents =
VALUES(printify_price_cents),
printify_sku = VALUES(printify_sku),
printify_synced_at =
UTC_TIMESTAMP()
");
foreach ($product['variants'] ?? [] as $variant) {
$normalized = normalizePrintifyVariant(
$variant,
$optionMap
);
$printifyVariantId =
$normalized['printify_variant_id'];
$seenIds[] = $printifyVariantId;
$existingStmt->execute([
':shirt_id' => $shirtId,
':printify_variant_id' =>
$printifyVariantId,
]);
$exists = (bool) $existingStmt->fetchColumn();
$upsertStmt->execute([
':shirt_id' => $shirtId,
':color' => $normalized['color'],
':size' => $normalized['size'],
':price' =>
$normalized['price_cents'] / 100,
':enabled' =>
$normalized['enabled'] ? 1 : 0,
':available' =>
$normalized['available'] ? 1 : 0,
':printify_variant_id' =>
$printifyVariantId,
':cost_cents' =>
$normalized['cost_cents'],
':price_cents' =>
$normalized['price_cents'],
':sku' =>
$normalized['sku'] ?: null,
]);
$exists ? $updated++ : $inserted++;
}
markMissingVariantsUnavailable(
$pdo,
$shirtId,
$seenIds
);
return [
'inserted' => $inserted,
'updated' => $updated,
'seen' => count($seenIds),
];
}
Synchronize Product Images
Store image metadata separately so the storefront can distinguish front, back, sleeve, default, and color-specific mockups.
CREATE TABLE shirt_images (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
shirt_id BIGINT UNSIGNED NOT NULL,
printify_image_id VARCHAR(150) DEFAULT NULL,
position VARCHAR(50) DEFAULT NULL,
color VARCHAR(100) DEFAULT NULL,
image_url VARCHAR(1000) NOT NULL,
is_default TINYINT(1) NOT NULL DEFAULT 0,
sort_order INT NOT NULL DEFAULT 0,
printify_synced_at DATETIME DEFAULT NULL,
CONSTRAINT fk_shirt_images_shirt
FOREIGN KEY (shirt_id)
REFERENCES shirts(id)
ON DELETE CASCADE,
UNIQUE KEY uq_shirt_image_url (
shirt_id,
image_url(250)
),
KEY idx_shirt_images_shirt (
shirt_id
),
KEY idx_shirt_images_default (
shirt_id,
is_default
)
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci;
<?php
function syncPrintifyImages(
PDO $pdo,
int $shirtId,
array $product
): int {
$deleteStmt = $pdo->prepare("
DELETE FROM shirt_images
WHERE shirt_id = :shirt_id
");
$deleteStmt->execute([
':shirt_id' => $shirtId,
]);
$insertStmt = $pdo->prepare("
INSERT INTO shirt_images (
shirt_id,
printify_image_id,
position,
color,
image_url,
is_default,
sort_order,
printify_synced_at
) VALUES (
:shirt_id,
:printify_image_id,
:position,
:color,
:image_url,
:is_default,
:sort_order,
UTC_TIMESTAMP()
)
");
$count = 0;
foreach ($product['images'] ?? [] as $index => $image) {
$insertStmt->execute([
':shirt_id' => $shirtId,
':printify_image_id' =>
$image['id'] ?? null,
':position' =>
$image['position'] ?? null,
':color' =>
$image['color'] ?? null,
':image_url' =>
$image['src'],
':is_default' =>
!empty($image['is_default']) ? 1 : 0,
':sort_order' => (int) $index,
]);
$count++;
}
return $count;
}
Load Blueprint and Print Provider Data
Merchant products contain blueprint and provider IDs. Catalog endpoints add brand, model, provider, print-area, and option details.
<?php
function fetchBlueprint(int $blueprintId): array
{
return printifyRequest(
'GET',
sprintf(
'/catalog/blueprints/%d.json',
$blueprintId
)
);
}
function fetchBlueprintProviders(
int $blueprintId
): array {
return printifyRequest(
'GET',
sprintf(
'/catalog/blueprints/%d/print_providers.json',
$blueprintId
)
);
}
function fetchProviderVariants(
int $blueprintId,
int $providerId
): array {
return printifyRequest(
'GET',
sprintf(
'/catalog/blueprints/%d/print_providers/%d/variants.json',
$blueprintId,
$providerId
)
);
}
Store Print Areas and Production Data
Provider variant responses can include option combinations, placeholders, print-area dimensions, and decoration methods.
CREATE TABLE printify_print_areas (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
shirt_id BIGINT UNSIGNED NOT NULL,
printify_variant_id INT UNSIGNED NOT NULL,
position VARCHAR(100) NOT NULL,
decoration_method VARCHAR(100) DEFAULT NULL,
width_px INT UNSIGNED DEFAULT NULL,
height_px INT UNSIGNED DEFAULT NULL,
synced_at DATETIME NOT NULL,
CONSTRAINT fk_print_area_shirt
FOREIGN KEY (shirt_id)
REFERENCES shirts(id)
ON DELETE CASCADE,
UNIQUE KEY uq_print_area (
shirt_id,
printify_variant_id,
position,
decoration_method
)
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci;
Use a Database Transaction
The product, variants, images, and production records should either all update successfully or all roll back.
<?php
$pdo->beginTransaction();
try {
$shirtId = upsertPrintifyProduct(
$pdo,
$product
);
$variantResult = syncPrintifyVariants(
$pdo,
$shirtId,
$product
);
$imageCount = syncPrintifyImages(
$pdo,
$shirtId,
$product
);
$pdo->commit();
} catch (Throwable $exception) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
throw $exception;
}
Handle Missing and Disabled Variants
A variant that disappears from the latest API response should not remain purchasable locally.
<?php
function markMissingVariantsUnavailable(
PDO $pdo,
int $shirtId,
array $seenIds
): void {
if (!$seenIds) {
$stmt = $pdo->prepare("
UPDATE shirt_variants
SET
available = 0,
enabled = 0
WHERE shirt_id = :shirt_id
");
$stmt->execute([
':shirt_id' => $shirtId,
]);
return;
}
$placeholders = implode(
',',
array_fill(
0,
count($seenIds),
'?'
)
);
$sql = "
UPDATE shirt_variants
SET available = 0
WHERE shirt_id = ?
AND printify_variant_id
NOT IN ({$placeholders})
";
$stmt = $pdo->prepare($sql);
$stmt->execute([
$shirtId,
...$seenIds,
]);
}
Add Synchronization Logs
CREATE TABLE printify_sync_log (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
sync_type VARCHAR(50) NOT NULL,
status VARCHAR(30) NOT NULL,
products_seen INT UNSIGNED NOT NULL DEFAULT 0,
products_updated INT UNSIGNED NOT NULL DEFAULT 0,
variants_updated INT UNSIGNED NOT NULL DEFAULT 0,
images_updated INT UNSIGNED NOT NULL DEFAULT 0,
warning_count INT UNSIGNED NOT NULL DEFAULT 0,
error_message TEXT DEFAULT NULL,
started_at DATETIME NOT NULL,
finished_at DATETIME DEFAULT NULL,
KEY idx_sync_started_at (started_at),
KEY idx_sync_status (status)
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci;
Build the Complete Synchronization Script
<?php
/**
* /admin/sync-printify.php
*/
declare(strict_types=1);
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once __DIR__ . '/../../private/connection.php';
require_once __DIR__ . '/../../private/printify.php';
require_once __DIR__ . '/includes/admin-auth.php';
require_once __DIR__ . '/includes/printify-sync-functions.php';
requireAdmin();
set_time_limit(0);
$startedAt = gmdate('Y-m-d H:i:s');
$summary = [
'products_seen' => 0,
'products_updated' => 0,
'variants_updated' => 0,
'images_updated' => 0,
'warnings' => [],
];
try {
$productSummaries = fetchAllPrintifyProducts(
PRINTIFY_SHOP_ID
);
foreach ($productSummaries as $summaryProduct) {
$summary['products_seen']++;
$product = fetchPrintifyProduct(
PRINTIFY_SHOP_ID,
$summaryProduct['id']
);
$pdo->beginTransaction();
try {
$shirtId = upsertPrintifyProduct(
$pdo,
$product
);
$variantResult =
syncPrintifyVariants(
$pdo,
$shirtId,
$product
);
$imageCount =
syncPrintifyImages(
$pdo,
$shirtId,
$product
);
$pdo->commit();
$summary['products_updated']++;
$summary['variants_updated'] +=
$variantResult['seen'];
$summary['images_updated'] +=
$imageCount;
} catch (Throwable $exception) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
$summary['warnings'][] = sprintf(
'%s: %s',
$summaryProduct['id'],
$exception->getMessage()
);
error_log(
'Printify product sync failed: ' .
$summaryProduct['id'] .
' - ' .
$exception->getMessage()
);
}
}
recordPrintifySyncLog(
$pdo,
'full',
'completed',
$summary,
$startedAt
);
} catch (Throwable $exception) {
recordPrintifySyncLog(
$pdo,
'full',
'failed',
$summary,
$startedAt,
$exception->getMessage()
);
throw $exception;
}
Run Synchronization from the Admin Area
Synchronization Controls
The admin page should require authentication, show the current shop, display the last sync result, and require a deliberate action before starting a full import.
Part 4 Synchronization Checklist
- The Printify token is stored outside the public web root.
- The token has only the required access scopes.
- Every API request includes Authorization and User-Agent headers.
- All API calls run through PHP rather than browser JavaScript.
- The correct Printify shop ID is stored in configuration.
- Product pagination continues through the final page.
- Detailed product records are requested before synchronization.
- Products are matched by Printify product ID.
- Variants are matched by Printify variant ID.
- Prices are converted from cents correctly.
- Provider and blueprint IDs are stored separately.
- Enabled and available states remain separate.
- Missing variants are marked unavailable.
- Local SEO and merchandising fields are preserved.
- Product images are synchronized separately.
- Print areas and decoration methods can be stored.
- Each product sync uses a database transaction.
- One failed product does not stop the entire catalog import.
- Warnings and errors are written to private logs.
- The last synchronization time is visible in the admin area.
- Full synchronization requires authenticated access.
- Synchronization does not publish or change live products accidentally.
- API rate limits and retry behavior are respected.
- Tokens are rotated before expiration.
Printify Product Synchronization FAQ
What authentication does the Printify API use?
An individual merchant can use a Printify Personal Access Token. The token is sent as a Bearer token in the Authorization header, and requests must include a User-Agent header.
How do I find my Printify shop ID?
Request the list of shops from the shops endpoint, choose the correct shop, and store its numeric ID in private configuration. Product endpoints use that shop ID in their URLs.
Why should Printify requests run through PHP?
The Printify API does not support browser CORS requests, and the personal access token must remain private. A server-side PHP application safely stores the token and performs the API calls.
What is the difference between a blueprint and a product?
A blueprint is a blank catalog product such as a specific shirt model. A merchant product is created after artwork, variants, pricing, and print areas are configured for a shop.
What is a Printify print provider?
A print provider manufactures the selected blueprint. Providers may differ by location, available variants, print methods, print areas, production costs, and shipping options.
What price format does Printify use?
Many Printify API monetary values are represented as integers in the smallest currency unit, such as cents. Convert them carefully before storing or displaying dollar amounts.
Should Printify product data replace my local descriptions and SEO fields?
No. Synchronization should update operational fields such as platform IDs, variants, prices, images, and availability while preserving local storefront copy, collections, SEO, likes, and reviews.
How should deleted or unavailable Printify variants be handled?
Do not delete local rows immediately. Mark missing variants unavailable or disabled, record the synchronization time, and review changes before permanently deleting historical mappings.
How often should products be synchronized?
Run synchronization after important product edits and on a regular schedule appropriate for the catalog. Avoid unnecessary rapid polling and respect current API rate limits.
Why use database transactions during synchronization?
A transaction prevents the local product from being left partially updated when a variant, image, or database operation fails in the middle of the process.
Should the sync script publish products to Shopify?
Keep synchronization and publishing as separate operations. Importing Printify data should not automatically publish, change prices, or alter the live storefront unless that behavior is deliberate.
Can the Printify API provide catalog and shipping data?
Yes. Catalog endpoints expose blueprints, print providers, variants, print areas, and shipping information. Some shipping capabilities, including economy shipping cost listings, are available through the V2 catalog API.
Continue to Part 5
The local database can now receive and update Printify products, variants, images, providers, pricing, and production information. Part 5 turns those synchronization functions into practical private administration tools.