FS
← Back
● LIVE — Embedded Shopify App (Production + Staging)

Shopi Pulse

Shopify store optimization platform embedded directly in the Shopify admin: an AI agent reads live store data through the Admin API, reasons over multi-step tool calls, and executes changes (pricing, inventory, product content) behind tier-gated human-in-the-loop approvals. No login screen — the store itself is the identity.

NestJSNext.jsApp BridgePrismaPostgreSQLRedisTurborepoShopify APIClaude / OpenRouter
31AI Agent Tools
SSEStreaming Agent Loop
0Login Screens — Embedded Auth
3Billing Tiers (Shopify Native)

// Context

Shopify merchants sit on data they don't act on — pricing gaps, thin product descriptions, stale inventory. Shopi Pulse connects to a store via the Shopify Admin API and gives an AI agent a tool belt to inspect that data and propose concrete edits, instead of another static analytics dashboard.

// What I Built

pnpm/Turborepo monorepo: NestJS backend API and Next.js (App Router) dashboard as separate apps, sharing packages for AI tooling, Shopify client, Prisma schema, and workflow/automation types.
Streaming agent loop with tool use: Multi-turn agent service drives read tools (products, orders, inventory) and write tools (price/content updates) against the Shopify Admin API, streaming progress to the frontend over SSE with anti-buffering headers through the Cloudflare + reverse-proxy chain.
Embedded-only auth, no login: The app lives inside the Shopify admin iframe (App Bridge v4). Install is cookieless: the frontend's signed session token is exchanged server-side for an expiring offline Admin API token — no OAuth redirect, no user table, no passwords. Shop identity comes only from HMAC-verified session tokens, never from query params.
Tier-gated approval guardrails: Write actions land in a PendingAction queue for explicit approval, or auto-execute on paid tiers with a per-store toggle — tier limits enforced server-side on every request, independent of stored toggle state.
Native Shopify billing: Subscription flow through Shopify's Billing API (appSubscriptionCreate) with FREE/PRO/CUSTOM tiers, deduplicated billing webhooks, synchronous downgrade on cancel, and per-store daily usage limits.
Multi-provider LLM abstraction: Format-normalizing client layer supports Anthropic directly and OpenRouter as an Anthropic-compatible gateway, so model choice (Claude, GPT-4o, free-tier alternatives) is a config swap, not a rewrite.
Persistent chat with full event history: Tool calls, tool results, and model identity are persisted per message (not just final text), so a reloaded chat shows exactly what the agent did, not a collapsed summary.

// Architecture

Shopify admin iframeApp Bridge session tokenNext.js dashboardSSE chat streamNestJS agent service
tool-use loop (read/write tools)Shopify Admin APIPendingAction approval gate
Postgres + Prisma for persistence, Redis + BullMQ for store sync, native Shopify billing for subscriptions

// Key Engineering Decisions

Model identity surfaced, not hidden: Free-tier models fail multi-step tool use in distinct, confusing ways — hallucinated IDs, silent truncation, dead-end turns with no action taken. Rather than paper over every failure shape server-side, the model that generated each response is persisted and shown, turning 'the app is broken' into 'this model isn't strong enough for this task.'
Dead-end turns surfaced, not swallowed: The agent loop detects when a turn produces no tool call and no real answer (a stall) and raises it as an explicit error event instead of silently saving stale text as the final response.
SSE through a proxy chain, done properly: Real-time streaming was silently buffered by the Cloudflare + reverse-proxy path. Fixed with explicit X-Accel-Buffering headers and forced header flush rather than switching transport.
Reads are free, writes are tier-gated: Read tools execute immediately; write tools stage a PendingAction or auto-execute only when the store's billing tier allows it — enforcement lives in the per-request limit resolution, so a stale toggle left on after a downgrade can't bypass the gate.
The store is the identity: No merchant accounts, sessions, or refresh tokens to manage. Every request's shop comes from a short-lived HMAC-signed session token; expiring offline Admin API tokens are refreshed through a single choke-point service every caller (sync, billing, AI tools) must go through.
Live tests over mocks: An env-gated Jest suite runs all 31 agent tools against a real staging store. It caught two production GraphQL bugs that mocked unit tests masked — the mocks had encoded the broken schema. Write tools carry undo handlers so test mutations roll back.

// War Story

A production conversation looked frozen mid-task: the agent correctly diagnosed a conversion-optimization request, then went silent — no streaming, no tool calls, no error. Root cause turned out to be layered: SSE responses were buffered by the proxy chain (fixed with anti-buffering headers), and separately the tool-use loop could hit a turn with no tool call and no content, which the code was treating as a normal completion. Fixing both surfaced a further issue — a free-tier model truncating a response mid-table at 528 characters, invisible without knowing which model produced it. Model tracking closed that loop: every assistant message now carries the model that wrote it, so future failures are diagnosable from the chat itself.

app.pulse-ai.uk ↗
Shopi Pulse — Main Dashboard & Analytics

[01/04] — Store Analytics — Product performance, inventory pricing gaps, and action recommendations