Skip to content

Free Plan: Reply Cap and Monthly Reset

This document describes how the free plan reply limit is stored, enforced, and reset.


ColumnTypeDescription
free_credit_limitintegerMaximum 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_usedintegerNumber 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).

  1. Store creation
    When a new shop is created (OAuth install), free_credit_limit is set from process.env.FREE_PLAN_REPLIES_CAP (default 50). free_credits_used is set to 0.
    See app/shopify.server.js (shop create).

  2. Each AI reply (free tier path only)
    After an AI reply is sent, the app increments free_credits_used by 1 only when the request used the free reply cap (not the USD wallet). If the shop is on the wallet path (plan === "paid" or credit_balance > 0 while plan is free), incrementFreeCreditsUsed is skipped.
    See app/lib/limits.server.js (incrementFreeCreditsUsed, shopUsesCreditWallet) and app/lib/rag.server.js.

  3. Monthly reset (automatic cron)
    On UTC calendar day 1 each month a scheduled job GETs the reset endpoint. The route resets free_credits_used to 0 for all plan = 'free' shops in a single updateMany. Paid shops are not affected.

    Production URL (copy for your scheduler):
    https://ai-chat.appifire.com/api/cron/reset-free-plan-replies

    Use 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) from app/lib/limits.server.js or hit the URL above on UTC day 1.

    Ops reference (cron + compliance + analytics tables): Scheduled jobs, privacy & shop analytics.


  • Before generating an AI reply, the app checks the free plan limit in checkReplyLimit (in app/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_used and free_credit_limit so merchants can see usage and upgrade when needed.

  • FREE_PLAN_REPLIES_CAP
    Used only when a new shop is created. Its value is stored in shops.free_credit_limit for that shop. Default if unset: 50.
    Existing shops keep their current free_credit_limit; change this env only affects new installs.

  • Migration 20260310000000_add_free_credit_limit_and_used adds free_credit_limit (default 50) and free_credits_used (default 0) to shops.
    Existing shops get these defaults; new shops get free_credit_limit from 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:

  • shopUsesCreditWallet in app/lib/limits.server.js is true when plan === "paid" OR credit_balance > 0.
  • checkReplyLimit skips the 50-reply cap when the wallet path applies.
  • deductCreditsForReply reduces credit_balance even while plan === "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.