How to Sync OTC Retail Inventory Between Your Pharmacy System and Shopify
Run a nightly Spojit workflow that pulls front-of-store OTC and retail stock counts from your pharmacy point-of-sale system and pushes them into your Shopify storefront, so online stock matches the shelf.
What This Integration Does
Pharmacies that sell over-the-counter and general retail products online often end up with two stock figures that drift apart: the count in the in-store point-of-sale (POS) system and the count shown on the Shopify storefront. When they disagree, customers buy items that are out of stock, or popular products look sold out when they are still on the shelf. This Spojit tutorial keeps the two in step by reading current counts from your POS each night and adjusting the matching Shopify variants to match. This is a retail and admin operation only: it touches OTC and front-of-store product counts, never prescription records, dispensing data, or any clinical information.
The workflow runs on a Schedule trigger overnight. A Connector node on the http connector calls your POS REST API and returns a list of products with current quantities. A Transform node maps each POS SKU to its Shopify inventory item and location. A Loop node walks the list, reads the live Shopify count with get-inventory-levels, computes the difference, and applies it with adjust-inventory. A Condition node flags any item whose POS count is zero or negative so a person can review it. Each run leaves Shopify counts matching the POS snapshot taken that night; because every run reads the live Shopify level and applies a delta, re-running is safe and self-correcting rather than additive.
Prerequisites
- A Shopify connection in Spojit with permission to read and adjust inventory. See the Shopify connector article for setup.
- An http connection (or an API key you can pass in a header) for your pharmacy POS REST API. The http connector is built in and needs no auth of its own; you supply the endpoint and credentials per call.
- Your Shopify Location ID for the store location that fulfils online orders (numeric or GID form).
- A SKU mapping: for each POS SKU, the matching Shopify
inventoryItemId. Keep this as a lookup table you can reference in the Transform node. - Read access to a POS endpoint that returns current on-hand quantities for retail/OTC items (for example
GET /v1/inventory).
Step 1: Add a Schedule trigger to run nightly
On a new workflow, set the Trigger node type to Schedule. Use a 5-field cron expression and an IANA timezone so the run lands after the store closes and the POS day is settled. For example, run at 2am every day:
Cron: 0 2 * * *
Timezone: Australia/Sydney
The trigger output is { scheduledAt }, which you can reference downstream as {{ trigger.scheduledAt }} for logging. A single Schedule trigger can hold more than one schedule if you want a second mid-day sync.
Step 2: Pull current counts from your POS with the http connector
Add a Connector node in Direct mode on the http connector and choose http-get. Point it at your POS inventory endpoint and pass your API key in a header. Direct mode is the right choice here because this is a single, predictable call with no AI cost.
Tool: http-get
URL: https://api.your-pos.example.com/v1/inventory?category=otc
Headers: { "Authorization": "Bearer YOUR_POS_API_KEY", "Accept": "application/json" }
Name the output variable pos. The response body is available as {{ pos.data }}. If your POS returns CSV instead of JSON, add a Connector node on the csv connector with parse (then to-json) to turn it into rows before the next step. For a fuller walkthrough of reaching a non-native system, see How to Connect to Any REST API Using HTTP Requests.
Step 3: Map POS SKUs to Shopify variants with a Transform node
Add a Transform node to reshape the raw POS list into a clean array your loop can iterate. Each item needs the Shopify inventoryItemId, the target posQuantity, and a label for logging. Produce a structure like this:
[
{ "sku": "OTC-PARA-500", "inventoryItemId": "44910023", "posQuantity": 42 },
{ "sku": "OTC-VITD-1000", "inventoryItemId": "44910099", "posQuantity": 0 }
]
Use your SKU-to-inventory-item lookup here to attach the right inventoryItemId to each row, and drop any POS SKU that has no Shopify match so the loop never tries to adjust a product that does not exist online. If you prefer to do the join with explicit field tools, the json connector (get, pick, merge) is handy inside the Transform step. Name the output variable mapped.
Step 4: Loop over each item to read and reconcile Shopify stock
Add a Loop node in ForEach mode over {{ mapped }}. Inside the loop body, add a Connector node in Direct mode on the shopify connector with get-inventory-levels to read the live count for the current item:
Tool: get-inventory-levels
inventoryItemId: {{ item.inventoryItemId }}
Name this output levels. Next, add a Transform node inside the loop to compute the delta the adjustment needs. The adjust-inventory tool applies a change, not an absolute value, so subtract the current Shopify quantity at your fulfilment location from the POS target:
delta = {{ item.posQuantity }} - {{ levels.data.available }}
Reading the live level every pass is what makes re-runs safe: if a previous run already matched the count, the delta is zero and nothing changes.
Step 5: Apply the adjustment with the Shopify adjust-inventory tool
Still inside the loop, add a Connector node in Direct mode on the shopify connector with adjust-inventory. Pass the inventory item, your fulfilment locationId, the computed delta, and a reason so the change is auditable in Shopify:
Tool: adjust-inventory
inventoryItemId: {{ item.inventoryItemId }}
locationId: 71234567 (your online fulfilment location)
delta: {{ delta }}
reason: correction
The reason field accepts values such as correction, cycle_count_available, and restock; correction is the natural fit for a reconciliation sync. To avoid a needless API call when the counts already agree, wrap this node in a Condition node that only runs the adjustment when {{ delta }} is not equal to 0.
Step 6: Flag zero and negative counts with a Condition node
Add a Condition node, either inside the loop or after it on a collected list, that checks whether {{ item.posQuantity }} is less than or equal to 0. A zero count means the item is out of stock online (expected), but a negative count usually signals a POS data problem worth a human look. On the true branch, add a Send Email node to alert your retail operations inbox:
Recipients: retail-ops@yourpharmacy.example.com
Subject: OTC sync flagged {{ item.sku }} (count {{ item.posQuantity }})
Body: POS reported {{ item.posQuantity }} for {{ item.sku }} during the nightly sync on {{ trigger.scheduledAt }}. Please verify the shelf count.
Send Email uses Spojit's built-in mail service, so no connection is needed, but external recipients must be on your org allowlist under Settings → General → Email recipients. If you would rather post flags to a channel, swap in a Connector node on the slack connector with send-message.
Tips
- Keep the SKU mapping in one place (the Transform in Step 3) so onboarding a new OTC product is a single edit, not a change across several nodes.
- If your catalogue is large, page the POS call: most POS APIs accept
?page=or a cursor, so loop thehttp-getuntil the POS returns no more rows before you buildmapped. - Shopify enforces API rate limits. If you sync hundreds of items, add a small pause between loop passes or batch the work so
adjust-inventorycalls do not get throttled. - Ask Miraxa, the intelligent layer across your automation, to scaffold this for you with a prompt like "Add a Loop over
{{ mapped }}that reads Shopifyget-inventory-levelsand then callsadjust-inventorywith the delta," then fine-tune fields in the properties panel.
Common Pitfalls
- Treating delta as an absolute count.
adjust-inventoryapplies a change, not a target. Always subtract the live Shopify level (Step 4) before you call it, or counts will spiral. - Wrong location. Stores often have several Shopify locations. Adjusting the wrong
locationIdmoves stock on a location that does not fulfil online orders. Confirm the fulfilment location once and reuse it. - Unmapped SKUs. A POS SKU with no Shopify match will fail the adjustment. Drop unmatched rows in the Transform step rather than letting the loop error.
- Timezone drift. If the cron timezone does not match the store's, the sync can run before the POS day closes and capture mid-shift counts. Set the IANA timezone explicitly on the Schedule trigger.
Testing
Before turning the schedule on, validate on a tiny scope. Trim the Step 3 Transform to a single known OTC SKU, then use the Run button to execute the workflow manually. Confirm in the execution log that get-inventory-levels returned the count you expect, that the computed delta is correct, and that adjust-inventory moved the Shopify count to match the POS figure. Check the Shopify admin to see the new quantity and the correction reason on the inventory history. Once one item reconciles cleanly, restore the full mapping and let the Schedule trigger take over. See Understanding Execution Logs for reading run output.