The OPS push pipeline is the mechanism that takes a product selected for a customer storefront and writes it — with markup-adjusted pricing, size variants, and category assignment — into OnPrintShop via GraphQL mutations. The pipeline is split between FastAPI (data preparation and markup) and n8n (OPS API orchestration). Neither system acts alone: FastAPI never calls OPS directly, and n8n never applies pricing logic itself.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/VisualGraphxLLC/API-HUB/llms.txt
Use this file to discover all available pages before exploring further.
Prerequisites
Before triggering a push, verify the following are in place:Customer configured
A customer record exists with valid OPS OAuth2 credentials. Run
POST /api/customers/{id}/test to confirm connectivity.Products selected
At least one product is in
customer_product_selections for the customer with status selected or stale.n8n running
The n8n instance has the
ops-push workflow imported and the OnPrintShop credential configured for the target storefront.Secrets aligned
INGEST_SHARED_SECRET is set in both the FastAPI .env and as an n8n environment variable. The values must match exactly.Pipeline flow
The full push for a single product traverses these stages in sequence:Select products for a customer
In the admin UI, open a customer and add products to their selection list. Each product gets a row in
customer_product_selections with status = "selected". You can also filter candidates by supplier using GET /api/push/candidates/{customer_id}?supplier_id={uuid}&only_never_pushed=true.Trigger the n8n ops-push workflow
The workflow is triggered via a webhook GET request. You can trigger it from the admin UI (Workflows page) or directly:The workflow’s Parse Params node validates
customer_id (required) and constructs the product list URL. limit is clamped between 1 and 200.Fetch the product list
n8n calls
GET /api/products/{product_id} for a single-product push, or GET /api/products?supplier_id=...&limit=N for bulk. The Explode Products node normalizes the response into one item per product, each carrying api_base, customer_id, and product_id.Fetch the markup payload
For each product, n8n calls:FastAPI resolves the best matching markup rule for this customer–product pair and returns the full push payload including final per-variant prices. This is the only place in the pipeline where pricing logic executes.
Build OPS mutation inputs
The Build OPS Inputs n8n node assembles three JSON strings from the payload:
setProductCategory_input— category name and visibilitysetProduct_input— product title (fromproduct.name), internal title (fromsupplier_sku), and category ID (filled in after the next step)setProductPrice_template— price band using the minimumfinal_priceacross all variants as the starting price
OPS: setProductCategory
The OnPrintShop n8n node calls the
setProductCategory mutation to create or update the product’s category on the storefront. The returned category_id is injected into setProduct_input by the Attach Category ID node.If this mutation fails, the error branches to the Error Handler node rather than halting the entire workflow.OPS: setProduct
The
setProduct mutation creates or updates the product shell on OPS. It returns products_id — the OPS-side identifier used by every subsequent mutation in this run. The product title, internal title, and category ID are all set here.OPS: setProductSize (per variant)
The Build Size Inputs node splits the payload’s
variants array into one item per variant. For each, the setProductSize mutation registers the size/color combination and SKU on OPS. Falls back to a single "OS" (one-size) entry if the payload has no variants.OPS: setProductPrice
The
setProductPrice mutation writes the price band for the product using products_id from setProduct. The price field carries the minimum marked-up final_price; vendor_price carries the minimum base_price for cost tracking in OPS.Fetch and stub product options
After price is set, n8n calls
GET /api/push/{customer_id}/product/{product_id}/ops-options to retrieve option definitions. The Stub Apply Options node currently annotates options with null target IDs and passes through — setAdditionalOption and setAdditionalOptionAttributes mutations are not yet shipped by OPS. The stub will be replaced by live mutation nodes when those endpoints become available.Write push mappings
The Build Push Mapping node constructs a
push_mappings record linking each source master option to its OPS counterpart. This is written to POST /api/push-mappings for option mapping traceback. Even while the options stub is active, the mapping skeleton is persisted so it can be populated forward when OPS ships the mutations.Error handling
Errors from any OPS mutation node (setProductCategory, setProduct, setProductSize, setProductPrice) branch to the Error Handler code node. It extracts the error message and routes to POST Push Log Error, which writes astatus = "failed" row to product_push_log with the error text. The webhook still responds rather than hanging.
Push log and history
Every push attempt — success or failure — is recorded inproduct_push_log.
| Column | Type | Description |
|---|---|---|
id | UUID | Row identifier |
product_id | UUID | FK → products |
customer_id | UUID | FK → customers |
ops_product_id | string | OPS products_id returned by setProduct |
status | string | pushed / failed / skipped |
error | text | Error message (null on success) |
pushed_at | timestamptz | UTC timestamp of the attempt |
pushed_at DESC. The response includes ops_product_id so you can look up the item directly in OPS.
Global push log (paginated, joinable):
products, customers, and suppliers and returns product_name, customer_name, and supplier_name alongside each log entry.
Per-product status across all customers:
Image processing
When n8n needs to upload a product image to OPS viasetOrderProductImage, it calls the image processing endpoint to obtain a WebP-encoded version of the supplier’s source image:
product_images.url, processes it to WebP (using the image_pipeline module), and returns the bytes with Content-Type: image/webp and a 24-hour cache header. If the source URL is unreachable, a 502 is returned; processing failures return 500.
Image IDs are UUIDs from the
product_images table, not numeric OPS image IDs. The correct ID to pass is the hub-side ProductImage.id.Push status lifecycle
A product’s status incustomer_product_selections follows a defined state machine:
selected
Product is in the push queue. No
ops_product_id exists yet. Waiting for a push to be triggered.pushed
Product exists on the OPS storefront.
ops_product_id in product_push_log links to the OPS record.stale
The catalog was re-synced after the last push. Product data on OPS may be outdated. Re-push to refresh pricing, inventory, and variant data.
Environment variables
The push pipeline depends on two environment variables being consistent across FastAPI and n8n:Activating the workflow
Theops-push.json workflow ships with "active": false. You must activate it manually after importing it into n8n and binding the OnPrintShop credential.
Import the workflow
In the n8n editor, go to Workflows → Import from File and select
n8n-workflows/ops-push.json.Bind the OnPrintShop credential
Open the workflow and click the OPS: Set Product Category, OPS: Set Product, OPS: Set Product Size, and OPS: Set Product Price nodes. In each, select (or create) the OnPrintShop credential that corresponds to the target storefront — it must use the same
ops_token_url, ops_client_id, and ops_client_secret as the customer record in API-HUB.Set environment variables
Confirm
API_BASE_URL and INGEST_SHARED_SECRET are present in the n8n environment.