Skip to main content

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.

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.

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:
1

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.
2

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:
# Single-product push
GET http://n8n-host:5678/webhook/ops-push?customer_id=<uuid>&product_id=<uuid>&limit=1

# Bulk push (all products for a customer, up to 25 at a time)
GET http://n8n-host:5678/webhook/ops-push?customer_id=<uuid>&limit=25
The workflow’s Parse Params node validates customer_id (required) and constructs the product list URL. limit is clamped between 1 and 200.
3

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.
4

Fetch the markup payload

For each product, n8n calls:
GET /api/push/{customer_id}/product/{product_id}/payload
X-Ingest-Secret: <INGEST_SHARED_SECRET>
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.
5

Build OPS mutation inputs

The Build OPS Inputs n8n node assembles three JSON strings from the payload:
  • setProductCategory_input — category name and visibility
  • setProduct_input — product title (from product.name), internal title (from supplier_sku), and category ID (filled in after the next step)
  • setProductPrice_template — price band using the minimum final_price across all variants as the starting price
6

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.
7

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.
8

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.
9

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.
10

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.
11

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.
12

Write push log

On success, n8n calls:
POST /api/push-log
Content-Type: application/json

{
  "product_id": "<uuid>",
  "customer_id": "<uuid>",
  "ops_product_id": "<ops-products-id>",
  "status": "pushed",
  "error": null
}
This creates an audit row in product_push_log. The customer_product_selections.status for this product flips to "pushed".

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 a status = "failed" row to product_push_log with the error text. The webhook still responds rather than hanging.
A failed push does not automatically retry. The product remains in selected or stale state. Check the push history in the admin UI, resolve the underlying issue (bad credentials, OPS outage, malformed payload), then re-trigger the push.

Push log and history

Every push attempt — success or failure — is recorded in product_push_log.
ColumnTypeDescription
idUUIDRow identifier
product_idUUIDFK → products
customer_idUUIDFK → customers
ops_product_idstringOPS products_id returned by setProduct
statusstringpushed / failed / skipped
errortextError message (null on success)
pushed_attimestamptzUTC timestamp of the attempt
Retrieve history for a product–customer pair:
GET /api/push/history/{customer_id}/{product_id}
Returns all log entries for the pair ordered by pushed_at DESC. The response includes ops_product_id so you can look up the item directly in OPS. Global push log (paginated, joinable):
GET /api/push-log?customer_id=<uuid>&limit=50
The global log joins to products, customers, and suppliers and returns product_name, customer_name, and supplier_name alongside each log entry. Per-product status across all customers:
GET /api/products/{product_id}/push-status
Returns the latest push log entry per customer for this product, enabling the product detail page to show which storefronts have the product and whether it is current.

Image processing

When n8n needs to upload a product image to OPS via setOrderProductImage, it calls the image processing endpoint to obtain a WebP-encoded version of the supplier’s source image:
GET /api/push/image/{image_id}/processed
The endpoint fetches the image URL stored in 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 in customer_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:
# FastAPI .env
INGEST_SHARED_SECRET=<random-32-char-string>
API_BASE_URL=http://host.docker.internal:8000   # n8n container → FastAPI host

# n8n environment (set in docker-compose.yml or n8n.cloud Secrets)
INGEST_SHARED_SECRET=<same-value-as-above>
API_BASE_URL=http://host.docker.internal:8000
If INGEST_SHARED_SECRET does not match between FastAPI and n8n, every call to /api/push/.../payload and /api/push/.../ops-options returns 401. The push log will record a failed status with an authentication error.

Activating the workflow

The ops-push.json workflow ships with "active": false. You must activate it manually after importing it into n8n and binding the OnPrintShop credential.
1

Import the workflow

In the n8n editor, go to Workflows → Import from File and select n8n-workflows/ops-push.json.
2

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.
3

Set environment variables

Confirm API_BASE_URL and INGEST_SHARED_SECRET are present in the n8n environment.
4

Activate and test

Toggle the workflow to Active. Trigger a single-product push using the admin UI Workflows page or the webhook URL directly, then check GET /api/push/history/{customer_id}/{product_id} to confirm a pushed log entry was written.

Build docs developers (and LLMs) love