Free Plan: Reply Cap and Monthly Reset
This document describes how the free plan reply limit is stored, enforced, and reset.
Database fields (shops table)
Section titled “Database fields (shops table)”| Column | Type | Description |
|---|---|---|
free_credit_limit | integer | Maximum number of AI replies allowed per month for this shop. Set when the store is created from .env FREE_PLAN_REPLIES_CAP (default 50). |
free_credits_used | integer | Number of AI replies used this month. Incremented by 1 each time the shop (on free plan) receives an AI reply. Reset automatically on UTC day 1 each month by the cron job (see below). |
When values are set or updated
Section titled “When values are set or updated”-
Store creation
When a new shop is created (OAuth install),free_credit_limitis set fromprocess.env.FREE_PLAN_REPLIES_CAP(default 50).free_credits_usedis set to 0.
Seeapp/shopify.server.js(shop create). -
Each AI reply (free tier path only)
After an AI reply is sent, the app incrementsfree_credits_usedby 1 only when the request used the free reply cap (not the USD wallet). If the shop is on the wallet path (plan === "paid"orcredit_balance > 0while plan is free),incrementFreeCreditsUsedis skipped.
Seeapp/lib/limits.server.js(incrementFreeCreditsUsed,shopUsesCreditWallet) andapp/lib/rag.server.js. -
Monthly reset (automatic cron)
On UTC calendar day 1 each month a scheduled job GETs the reset endpoint. The route resetsfree_credits_usedto0for allplan = 'free'shops in a singleupdateMany. Paid shops are not affected.Production URL (copy for your scheduler):
https://ai-chat.appifire.com/api/cron/reset-free-plan-repliesUse your real app origin if it differs (
SHOPIFY_APP_URL); path is always/api/cron/reset-free-plan-replies.- Cron expression (UTC):
10 0 1 * *(00:10 UTC, 1st of month). - No auth header needed. The route has a built-in UTC day-1 guard: if called on any day other than the 1st it returns
{ skipped: true }and does not write to the database — accidental calls on other days are completely safe. - Emergency manual reset: call
resetFreeCreditsUsedForFreePlanShops(prisma)fromapp/lib/limits.server.jsor hit the URL above on UTC day 1.
Ops reference (cron + compliance + analytics tables): Scheduled jobs, privacy & shop analytics.
- Cron expression (UTC):
Enforcing the limit (upgrade required)
Section titled “Enforcing the limit (upgrade required)”- Before generating an AI reply, the app checks the free plan limit in
checkReplyLimit(inapp/lib/limits.server.js). - If
free_credits_used >= free_credit_limit, the app throws REPLY_LIMIT_EXCEEDED and does not generate a reply. The API returns a 429 with a message that the monthly reply limit is exceeded and the merchant should upgrade to the paid plan. - The Billing page shows “X of Y replies used” for free plan using
free_credits_usedandfree_credit_limitso merchants can see usage and upgrade when needed.
Environment variable
Section titled “Environment variable”- FREE_PLAN_REPLIES_CAP
Used only when a new shop is created. Its value is stored inshops.free_credit_limitfor that shop. Default if unset: 50.
Existing shops keep their currentfree_credit_limit; change this env only affects new installs.
Migration
Section titled “Migration”- Migration
20260310000000_add_free_credit_limit_and_usedaddsfree_credit_limit(default 50) andfree_credits_used(default 0) toshops.
Existing shops get these defaults; new shops getfree_credit_limitfrom env at create time.
Free plan with a leftover USD wallet (after cancel)
Section titled “Free plan with a leftover USD wallet (after cancel)”If shops.plan is free but credit_balance > 0 (merchant canceled subscription but had not spent all purchased/included dollars), chat uses the credit wallet path, not the free monthly reply cap, until the balance reaches zero:
shopUsesCreditWalletinapp/lib/limits.server.jsis true whenplan === "paid"ORcredit_balance > 0.checkReplyLimitskips the 50-reply cap when the wallet path applies.deductCreditsForReplyreducescredit_balanceeven whileplan === "free".- After balance hits 0, behavior returns to the normal free reply cap (
free_credits_used/free_credit_limit).
Merchants cannot buy top-up packs without an active Shopify subscription (POST /app/billing/buy-credits checks activeSubscriptions). Full detail: Credits wallet: one-time & subscription.