Complete Product & Technical Reference
ShowcaseAI Documentation
Everything about the application in one place — architecture, technology, every feature, the credit and payment systems, the full API, the data models, and how it all fits together. Written to be read start-to-finish or used as a lookup reference.
Overview #
ShowcaseAI is a self-hosted SaaS platform for creating AI-generated marketing media — videos, images, voiceovers, avatars, and ad copy — sold on the CodeCanyon / Envato marketplace.
Users generate content by describing what they want or uploading a product photo. Behind the scenes the platform orchestrates three external AI services to produce talking-avatar videos, product showcase clips, images, cloned voices, and written ad copy. Usage is metered with a credit balance that users top up via Stripe.
Core capabilities
- Video generation — talking-avatar videos (lip-synced), product showcase, product-avatar, fashion/try-on, and AI ad videos.
- Image generation — text-to-image, product shoots, avatar imagery, and avatar+product compositing.
- Voice — text-to-speech in many voices, plus custom voice cloning.
- AI copywriting — ad scripts, marketing/sales copy, and a multi-variant Ad Copy Studio.
- Brand Kits — reusable brand identity applied across generations.
- Ad Localizer — recreate a talking-avatar video in many languages.
- Community — social feed with posts, likes, and comments.
- Billing — credit ledger + Stripe credit-pack purchases.
- Admin — users, analytics, preset prompts, avatar templates, community moderation, credit packs, and a Settings screen where all provider keys are entered (no file editing needed).
New here? Read Requirements, then follow Installation & Running. The interactive npm run setup
command creates your configuration, your admin account, and the starter data for you.
Architecture #
Two independent applications in one package (the backend/ and
frontend/ folders), communicating over HTTP.
| App | Role | Tech | Port |
|---|---|---|---|
| backend/ | Backend — business logic, database, AI orchestration, auth, credits, payments | Node.js + Express 5 + Mongoose | 5000 |
| frontend/ | Frontend — the browser UI users interact with | React 19 + Vite 7 | 4001 |
How a request flows
- The user clicks something in the browser (frontend, port 4001).
- The frontend's
API.request()helper sends an HTTP call to the backend atVITE_API_URL(e.g.http://localhost:5000/api/…), attaching the JWT token from theauth_tokencookie in theAuthorization: Bearer <token>header. - The backend authenticates the token (
checkAuthmiddleware →req.user;/admin/*routes additionally passcheckAdmin), runs the controller, talks to MongoDB and/or an AI provider, and returns JSON. - For slow jobs (video/image), the backend returns immediately with a "processing" record; the provider later calls back (webhook) or is polled, and the record flips to "completed."
Token-based auth: there are no server-side session objects. Every request carries a JSON Web Token (JWT) that the backend verifies, together with a per-user token version stored on the user record so that logout, password changes and admin actions can revoke sessions instantly. Both apps must run at the same time (or the API can serve the built frontend itself — see Production Deployment).
Technology Stack #
Backend backend/
| Concern | Library |
|---|---|
| Runtime / framework | Node.js 20.19+ (22 LTS recommended; an .nvmrc for Node 22 is included), ES modules, Express 5 |
| Database | MongoDB via Mongoose 8 |
| Auth | jsonwebtoken (JWT), bcryptjs (password hashing) |
| File uploads | express-fileupload → AWS S3 (@aws-sdk/client-s3, lib-storage) |
| Payments | stripe |
| AI / HTTP | axios, form-data (Kie's OpenAI-compatible Gemini endpoint is called over plain HTTP) |
| Security | helmet (security headers), express-rate-limit, cors |
| Mandrill (@mailchimp/mailchimp_transactional), nodemailer | |
| Media | music-metadata (audio duration) |
| Dev | node --watch (built in) |
Frontend frontend/
| Concern | Library |
|---|---|
| Framework / build | React 19, Vite 7 (dev host 0.0.0.0, port 4001) |
| Routing | React Router 7 (data router, createBrowserRouter) |
| State | Zustand 5 with persist → sessionStorage (<APP_NAME>-storage). Only the
user profile is persisted; the login token lives only in the auth_token cookie. |
| UI | Ant Design 5 (+ @ant-design/icons), Bootstrap 5 + react-bootstrap |
| Forms | react-hook-form |
| Media | wavesurfer.js (waveforms), react-h5-audio-player |
| Charts, drag and drop, content | react-apexcharts, @dnd-kit, react-markdown, react-infinite-scroll-component, re-resizable |
| Misc | js-cookie, nanoid, dayjs, dompurify, @hotjar/browser (optional, disabled when
VITE_HOTJAR_ID is empty) |
Project Structure #
The download contains two applications plus a quick-start README. Paths below are relative to the folder you unzipped.
README.md # Quick start documentation.html # This documentation (open in a browser) testing-guide.html # Step-by-step testing guide backend/ # Backend (Express + MongoDB) ├── server.js # Entry — starts the HTTP server ├── package.json # npm scripts (dev, start, setup, check…) ├── .nvmrc # Node.js 22 (nvm use) ├── .env.example # Template for .env ├── .env # YOUR configuration — not in the package; created by npm run setup. Never share it ├── src/ │ ├── app.js # Express app, security middleware, /api/health, Stripe webhook mount │ ├── routes.js # API route registry (all /api/* except /api/health and the Stripe webhook, which are in app.js) │ ├── controllers/ # Request handlers (+ webhook/ subfolder) │ ├── models/ # Mongoose schemas (26 models) │ ├── helpers/ # CreditHelper, SettingsHelper, LlmHelper, UploadHelper, Middleware… │ ├── lib/ # AI providers: kieLib, tts, newportai, render, stripe │ └── emailTemplates/ # resetPassword.html, userCreated.html ├── public/logo.png # Logo shown in emails (served at <BACKEND_URL>/logo.png) ├── tmp/ # Temporary upload files (cleaned automatically) ├── scripts/ # setup, check, security-smoke (+ internal helpers) ├── seed/ # Starter data loaded by npm run setup └── deploy/ ├── ecosystem.config.cjs # PM2 process file └── nginx.conf.example # nginx reverse-proxy example frontend/ # Frontend (React 19 + Vite 7) ├── .nvmrc # Node.js 22 (nvm use) ├── .env.example # Template for the VITE_* settings ├── .env # Your VITE_* settings — not in the package; created by npm run setup (backend) ├── vite.config.js # Dev server (port 4001) ├── public/assets/images/ # favicon, logo and other static images └── src/ ├── routes/ # index / userRoutes / adminRoutes / authRoutes ├── pages/ # user/, admin/, user/create/, user/tool/, auth/ ├── components/ # common/, avatars/, AiAds/, Popups/, profile/ ├── store.js # Zustand auth/user state └── utils/ # API.js (fetch wrapper), Helper.js
Backend npm scripts (run inside backend/)
| Command | What it does |
|---|---|
npm run setup |
Interactive installer: creates backend/.env and frontend/.env from the .env.example files (MongoDB URL, generated secrets, API URL), connects to MongoDB, creates the
admin and loads the starter data. See Installation. |
npm run dev |
Starts the API and restarts it automatically when code changes (Node's built-in --watch). For development. |
npm start |
Starts the API with plain Node.js. For production (usually through PM2). |
npm run check |
Installation health check (configuration, database, admin, starter data, settings). |
npm run smoke |
Runs the automated security checks (scripts/security-smoke.js, which you can also start with
node scripts/security-smoke.js) against a running API. See Security. |
Requirements & Technical Specifications #
What you need before installing. Items marked optional can be added later — the app starts without them and simply hides or disables the related feature.
Third-party services & costs — not included with your purchase. ShowcaseAI connects to these third-party services:
- Kie.ai — AI video and image models, ad copy and translation (required, paid).
- AWS S3 — storing uploads and generated media (required, paid).
- Speechify — voice library, text-to-speech and voice cloning (paid).
- NewPortAI — lip-sync for talking videos and the Ad Localizer (paid).
- Stripe — selling credit packs (optional, Stripe fees apply).
- SMTP or Mandrill — welcome and password-reset emails (optional).
- Pixabay, Unsplash, Pexels — stock image/video search (optional, free keys).
Accounts, API keys and usage fees for these services are not included with the purchase. You sign up with each provider yourself and pay them directly, according to their pricing. The AI features do not work until the required keys are entered in Admin → Settings. Prices and availability of these services are set by the providers and can change.
| Requirement | Needed for | Notes |
|---|---|---|
| A server or VPS that can run Node.js | Hosting the API (and the built frontend) | Any Linux, Windows or macOS server or VPS. Shared PHP hosting is not enough — it cannot run a Node.js process. |
| Node.js 20.19+ (22 LTS recommended) and npm 10+ | Running both applications | Check with node -v and npm -v. Download from nodejs.org. The .nvmrc
files in backend/ and frontend/ select Node 22 (with nvm on macOS/Linux, run
nvm use). |
| MongoDB 6.0+ | All application data | Self-hosted or a MongoDB Atlas cluster. On Atlas, add your server's IP address under Network Access (IP allow-list), otherwise the connection is refused. |
| A reverse proxy with HTTPS (production) | Serving the app and the API on your domains | An nginx example (backend/deploy/nginx.conf.example) and a PM2 process file
(backend/deploy/ecosystem.config.cjs) are included. See Production
Deployment. |
| AWS S3 bucket | Storing uploaded and generated media | Files must be publicly readable (bucket policy, or ACLs) because the AI providers download them. See Admin → Settings → Storage. |
| Kie.ai API key | All image and video generation, and all AI text (scripts, ad copy, translation) via Kie's Gemini endpoint | From your kie.ai account → API keys. |
| Speechify API key | Text-to-speech and voice cloning | From the Speechify API dashboard. |
| NewPortAI API key | Lip-sync talking avatars and the Ad Localizer | From newportai.com. |
| Stripe account (optional) | Selling credit packs | Secret key + webhook signing secret. |
| SMTP server or Mandrill (optional) | Password-reset and welcome emails | Without it, emails are skipped and a warning is logged. |
| Pixabay / Unsplash / Pexels keys (optional) | Stock media search | Each one is independent. |
| A public https URL for the API | Receiving generation results from Kie by webhook | Set as BACKEND_URL. For local testing use a tunnel such as ngrok. |
Technical specifications
| Part | Technology |
|---|---|
| Backend | Node.js 20.19+ (22 LTS recommended), Express 5, Mongoose 8 |
| Frontend | React 19 + Vite 7, built into static files (npm run build); runs in modern browsers
(current Chrome, Edge, Firefox, Safari) |
| Database | MongoDB 6.0+ (self-hosted or MongoDB Atlas) |
| Storage | AWS S3 bucket |
| Production server | Any Linux/Windows/macOS server or VPS with a reverse proxy and HTTPS; PM2 and nginx examples in
backend/deploy/ |
Why a public URL? Kie.ai sends finished images and videos back to <BACKEND_URL>/api/kie/webhook. On your own computer http://localhost:5000 is not reachable from the internet, so generations stay
"Processing" (credits are returned automatically after RESERVATION_TTL_MIN,
default 120 minutes). For local testing run a tunnel, for example ngrok http
5000, and set BACKEND_URL to the https://…
address it prints (in Admin → Settings → Site & Branding, or in backend/.env).
Installation & Running #
Install the backend first, then the frontend. The whole process takes about ten minutes. Run the commands in a terminal (on Windows: PowerShell or Command Prompt).
Secrets – set once, never change. Run npm run setup before you use the app or save any key in Admin → Settings.
SETTINGS_ENCRYPTION_KEYencrypts every provider key you save in Admin → Settings (AES-256-GCM; the encryption key is derived from it, andJWT_SECRETis used if it is not set). If it changes, every saved key becomes unreadable and the related features stop working. The saved values stay in the database, so putting the original value back (and restarting) makes them readable again; otherwise you must enter all keys again.- Changing
JWT_SECRETlogs every user out. - Keep a backup of
backend/.env. If the file is lost while the database still holds data, restore the same values — a newly generated key cannot decrypt the old data. --rotate-secrets(an option ofnpm run setup) replaces onlyJWT_SECRET; it never touchesSETTINGS_ENCRYPTION_KEY.
1 · Install the backend packages
$ cd backend $ npm install
2 · Run the interactive installer
$ npm run setup
The installer does everything needed for a working first start:
- Checks your Node.js version.
- Creates
backend/.envfrombackend/.env.example. The package contains no.envfile — no database URL or secret is included. Ifbackend/.envalready exists, setup keeps it and only fills in the missing values. - Asks for the MongoDB connection string. Press Enter for a local MongoDB (
mongodb://127.0.0.1:27017/showcaseai) or paste your own, for example an Atlasmongodb+srv://…URL (see how to get one). It also asks for the FRONTEND_URL (defaulthttp://localhost:4001) and the BACKEND_URL (defaulthttp://localhost:5000). - Generates a random
JWT_SECRETand a randomSETTINGS_ENCRYPTION_KEYand writes them into.env. - Creates
frontend/.envfromfrontend/.env.example, withVITE_API_URL=<your BACKEND_URL>/api/, if it does not exist yet. - Connects to MongoDB.
- Creates the admin account. It asks for the admin email (default
admin@showcaseai.app), name and password. There is no default password: leave it empty and setup generates a strong random one and prints it once at the end — save it. Public demo passwords such asUser@123#are refused. - Loads the starter data and one demo user — the demo user only when
NODE_ENVis notproduction(see Admin Account & Starter Data). Content such as prompts and avatar templates is added by you afterwards (see Add your own content). - Prints the logins and the next steps.
Run it once. If setup stops halfway (for example MongoDB is not running), fix the problem and run
it again. Once it has finished, running npm run setup again stops with
"already set up" and changes nothing — see Admin Account & Starter Data
for how to change things afterwards, or how to start over with --reset.
Non-interactive mode (servers, scripts): npm run setup -- --yes asks no
questions and uses the values already in .env, or the defaults. There is no
default database URL in this mode: mongoDBURL must already be set in
backend/.env, or you pass --mongo <url>.
Without --admin-password it generates a random admin password and prints it
once (save it); without --admin-email the admin is admin@showcaseai.app.
Pass your own on a live server. Options:
| Option | Effect |
|---|---|
--yes |
Do not ask questions; use .env values or defaults. Needs mongoDBURL in
.env or --mongo. |
--no-demo |
Create the admin and load starter data only — no demo user. (With NODE_ENV=production in backend/.env the demo user is never created.) |
--mongo <url> |
MongoDB connection string. Required with --yes unless mongoDBURL is already
in .env. |
--admin-email, --admin-password, --admin-name |
The admin account created by the first install (instead of answering the questions).
Environment alternatives: ADMIN_EMAIL, ADMIN_PASSWORD,
ADMIN_NAME. With --yes and no password given, setup generates a random
admin password and prints it once — save it. |
--frontend-url, --backend-url |
Public URLs of the frontend and of the API (default http://localhost:4001 and
http://localhost:5000). Editable later in Admin → Settings → Site & Branding. |
--rotate-secrets |
Generate a new JWT_SECRET during an install. It never changes SETTINGS_ENCRYPTION_KEY. |
--reset |
Deletes ALL data in the database (users, media records, payments, settings and saved keys)
and installs again. Shows what will be deleted and asks you to type the database name to confirm.
Non-interactive: --reset --confirm=<database name>. |
Example:
$ npm run setup -- --yes --no-demo --mongo "mongodb://127.0.0.1:27017/showcaseai" \
--admin-email you@example.com --admin-password "choose-a-strong-password" --admin-name "Your Name"
3 · Start the backend
$ npm run dev # development (restarts on code changes) $ npm start # production
The API listens on http://localhost:5000. Check it is alive by opening http://localhost:5000/api/health in a browser — it answers {"status":"success","message":"ok"}.
4 · Install and start the frontend
Open a second terminal (leave the backend running):
$ cd frontend $ npm install $ npm run dev # http://localhost:4001
frontend/.env was created by npm run setup, with VITE_API_URL=http://localhost:5000/api/ (your Backend URL + /api/), so it points at the backend. If the file is missing, the frontend still uses http://localhost:5000/api/; to change it, copy .env.example to .env and edit it. Open http://localhost:4001
in a browser.
5 · Log in and enter your provider keys
- Log in with the admin account you created in step 2 — the email you entered (default
admin@showcaseai.app) and the password you entered, or the generated one setup printed. - The AI features need two things. (a) Your own, paid provider keys entered in Admin → Settings:
Kie.ai, AWS S3, Speechify and NewPortAI (see Third-party services & costs).
(b) A public https
BACKEND_URL(Admin → Settings → Site & Branding), because Kie.ai sends finished results to<BACKEND_URL>/api/kie/webhook. On localhost, videos, avatars and images stay "Processing". For local testing run a tunnel such asngrok http 5000and setBACKEND_URLto the https address it shows — see Why a public URL?. - Open Admin → Settings.
- Enter your Kie.ai, Speechify and NewPortAI keys, your S3 storage details and (optionally) Stripe, email and stock-media keys. Click Save.
- Click each Test button to confirm the key works. See Admin → Settings for every field.
- Add your own content — prompts, avatar templates, community posts: Add your own content (manual).
6 · Verify the installation
$ cd backend $ npm run check
npm run check verifies that .env is valid, the
database connection works, an admin exists and the starter data is loaded. It lists which settings are
configured (it never prints their values) and warns if BACKEND_URL is still
localhost.
The database is yours. ShowcaseAI does not include a hosted database, and the package contains no MongoDB URL, password or secret. Use a local MongoDB server or your own MongoDB Atlas cluster and enter its connection string during setup.
Admin Account & Starter Data #
There is one way to install: npm run setup. It creates the
admin account and a small set of starter data for you — there is nothing to import by hand. After that,
you change everything from the admin panel and add your own content. Visitors can register themselves on the
sign-up page (/register), and admins can also create users in
Admin → Users.
What npm run setup does
$ cd backend $ npm install $ npm run setup
- Creates
backend/.envfrombackend/.env.example(the package contains no.envfile) and generates the two secrets. - Asks for your MongoDB connection string and site URLs (press Enter to accept the defaults), and
creates
frontend/.envpointing at your Backend URL. - Creates the admin account with the email, name and password you type. Leave the password empty to get a generated one, printed once at the end — save it. There is no default admin password.
- Loads the starter data (Creator Studio models, community categories, credit packs, dropdown lists) and one demo user (not when
NODE_ENV=production). - Prints the login details.
Setup runs once. Running npm run setup on an installed database stops
with "already set up" and changes nothing, so it can never overwrite your work. To start completely
fresh, use npm run setup -- --reset (see below).
After setup — change whatever you want
| To change… | Go to |
|---|---|
| Your admin password | Profile |
| Your admin email or name | MongoDB Compass. Admin accounts are not listed in Admin → Users (see Changing the admin login). |
| Customer accounts: name, password, credits and active/inactive status | Admin → Users (a user's email cannot be edited) |
| API keys, site URLs, email, AI text price | Admin → Settings |
| Credit packs and prices | Admin → Credit Packs |
| Community categories | Admin → Community Categories |
| Preset prompts | Admin → Prompts |
| Library avatars | Admin → Product / Video Avatar Templates |
| Creator Studio model prices | The aimodelcatalogs collection in your database (any MongoDB tool) |
Changing the admin login
There is exactly one admin account. Change it like this:
| To change… | How |
|---|---|
| The admin password | Log in → Profile → Change password. Forgot it? Use Forgot password on the login page (needs email set up in Admin → Settings → Email). |
| The admin email or name | Edit the admin in the database with MongoDB Compass (steps below). |
| Everything back to a clean install | npm run setup -- --reset — deletes all data after you confirm. |
Edit the admin with MongoDB Compass
- Install MongoDB Compass (free) from
https://www.mongodb.com/products/tools/compass. - Click Add new connection, paste the
mongoDBURLfrombackend/.envand connect. - Open your database (e.g.
showcaseai) → the users collection. - Filter with
{ "role": "admin" }, click the pencil icon on the document, changeemail(lower-case) and/orname, then Update. Log in with the new email.
Locked out and email is not set up?
- In a terminal inside
backend, create a password hash (replace the password):$ node -e "console.log(require('bcryptjs').hashSync('MyNewPassword123', 10))" - In Compass, edit the admin document and replace the value of
passwordwith the printed text (it starts with$2), then Update. - Log in with the new password and change it again from Profile if you like.
Only edit what is described here. Do not change role, _id or other fields of
the admin document, and never create a second admin.
Starting over (reset)
$ npm run setup -- --reset
Setup lists what will be deleted — every user account, video and image record, payment record, credit,
setting and saved API key — and asks you to type the database name to confirm. Nothing is deleted
unless you confirm. Then it installs everything fresh, exactly like the first time. Files already uploaded to
S3 are not removed. Make a backup first if you might need the data (e.g. mongodump).
Logins after setup
| Password | Role | Credits | |
|---|---|---|---|
The email you entered in setup (default admin@showcaseai.app) |
The password you entered, or the generated one setup printed | Admin | — (admins don't use credits) |
user@showcaseai.app |
User@123# |
User (demo) | 500 |
Passwords are stored as bcrypt hashes. The demo user is for local testing only. Its password is
public, so delete it in Admin → Users before you go live. It is never created when
NODE_ENV=production is set in backend/.env (or with
--no-demo).
Starter data (what setup creates)
| Collection | Contents |
|---|---|
aimodelcatalogs |
11 Creator Studio models. Video: seedance1-lite, seedance1-pro,
seedance1-pro-fast, kling21-statndard, veo3-fast,
grok-imagine-15. Image: z-image, nano-banana,
seedream45, flux2-pro, gpt-image15. Each has example
credit prices (pricingMatrix per resolution/duration, or costPerSecond) —
adjust them to your margins. |
sitedatas |
Ethnicity and profession dropdown lists used by the admin avatar-template form. |
communitycategories |
4 community categories (you can edit, reorder, add and delete them). |
creditpacks |
3 packs: Starter (100 credits, $9), Pro (500 credits, $39), Business (2000 credits, $129). |
users, credits |
The admin, plus one demo user and that user's 500 credits (skip the demo user with npm run setup -- --no-demo; it is never created when NODE_ENV=production). |
What setup does NOT create: prompts, avatar templates, community posts, brand kits, voices and generated images or videos. A fresh installation starts without them — you add the ones you want from the admin panel (see Add your own content (manual)). Uploading avatar templates requires S3 storage to be configured.
Add your own content (manual) #
A fresh installation contains only the admin, one demo user, the AI model catalog, community categories, credit packs and dropdown lists. Everything below is added by you, from the admin panel, in a few minutes. Log in as the admin first.
Prompts Admin → Prompts
Preset prompts appear in the prompt picker of the studios so users can start from a ready-made idea. Until you add some, that picker is empty (users can still write their own prompt).
- Open Admin → Prompts in the sidebar.
- Use the filter at the top to choose the prompt type (Fashion Shoot Video, Product Shoot Video, Product Shoot Image, Product Avatar Image or Image To Avatar). The list shows only that type.
- Click Add Prompt.
- Choose the Prompt Type, type a Title and write the full Prompt text (all three are required).
- Optional: under Reference Image click the box and choose a PNG, JPG or WEBP file (under 5 MB). Uploading needs S3 storage (Admin → Settings → Storage).
- Click Submit. To change a prompt later use the edit (pencil) icon; Update Prompt saves it. The trash icon asks for confirmation and deletes it.
Avatar templates Admin → Product Avatar · Video Avatar
The ready-made avatars users pick in Avatar Studio. Product Avatar templates are used by the product/fashion avatar tools; Video Avatar templates by the talking-avatar tools. Both pages work the same way and both need S3 to be configured.
- Open Admin → Product Avatar or Admin → Video Avatar.
- Click Add Product Avatar Template / Add Video Avatar Template.
- Fill in the details first: Full Name, Age Range, Ethnicity, Profession (the dropdown lists come from the starter data) and Gender, then Voice Type and Voice Name. Product Avatar templates also have an Avatar Type (Fashion Avatar or Product Avatar).
- Click Save Details. The upload boxes (image, video, matted video, thumbnail) become active only after the details have been saved once.
- Upload the files, then use the status switch in the list to publish or hide the template. Edit or delete it from the same list.
Community categories and posts Admin → Community Categories · Community
- Admin → Community Categories lists the 4 starter categories. Click + Add Category, enter a Name (required), an optional description and an optional Icon SVG (paste SVG code), then save. Drag rows to change their order; use the pencil or trash icons to edit or delete.
- Admin → Community opens the community feed. Click + Start Discussion, enter a Title, optional Description, pick a Category (required), add optional hashtags and up to 3 JPEG/PNG images, then post.
- As admin you can also pin a post to the top of the feed or remove any post. Posts by your users appear here automatically.
Credit packs Admin → Credit Packs
Setup creates three example packs (Starter, Pro, Business). Adjust them to your own prices.
- Open Admin → Credit Packs.
- Click Add Credit Pack (or edit an existing pack).
- Enter the Name, an optional description, the number of Credits and the Price; leave Currency as
usdor change it; set a Sort Order; tick Mark as "Most Popular" to highlight a pack. - Click Create (or Update). Active packs appear on the users' Buy Credits page; real payments need the Stripe keys in Settings.
Provider keys Admin → Settings
- Open Admin → Settings. The settings are split into groups (Site & Branding, AI Providers, Storage (AWS S3), Email / Notifications, Payments (Stripe), Credit Prices, Limits & Security, Stock Media); the links at the top jump to a group.
- Paste the keys you have (see Keys & Credentials for where to get each one). Leave a field empty to keep its current value.
- Click Save Settings at the bottom, then press the Test button next to each provider. Test checks the saved value, so save first.
Secrets – set once, never change. Save provider keys only after npm run setup has run. If SETTINGS_ENCRYPTION_KEY (in backend/.env) ever changes, all keys saved here become unreadable and show as not configured. Restore the original value and restart to get them back; otherwise you would have to re-enter every key. Details.
Other things you may want to add
- Users and credits: Admin → Users (add users, give credits).
- Model prices: the example credit prices of the Creator Studio models live in the
aimodelcatalogscollection (see Starter data). - Brand kits, voices and generations are created by users in their own studios and need no admin setup.
Configuration (.env) #
Server settings live in backend/.env (backend) and frontend/.env (frontend). The package contains only the .env.example templates; npm run setup creates both .env files from them. Never commit or share them.
Secrets – set once, never change. JWT_SECRET and SETTINGS_ENCRYPTION_KEY are empty in backend/.env.example; npm run setup generates them once. Changing SETTINGS_ENCRYPTION_KEY makes every key saved in Admin → Settings unreadable until the original value is restored. Changing JWT_SECRET logs everyone out. Back up backend/.env — if it is lost, restore the same values; a new key cannot decrypt old data. Details.
Provider keys belong in Admin → Settings. Keys for Kie, Speechify, NewPortAI, Stripe, S3, email and
stock media are best entered in the admin panel (stored encrypted, applied without a restart). The same
names in .env only act as a fallback. See Admin →
Settings, and Keys & Credentials for where to get every value.
Backend — backend/.env
This file is not in the package. npm run setup creates it from backend/.env.example and fills in mongoDBURL, JWT_SECRET and SETTINGS_ENCRYPTION_KEY for you (once). It only holds what the server needs
before it can start; everything else — site URLs, API keys, email, payments, storage, credit prices,
session length, rate limits and upload limits — is managed in Admin → Settings.
| Variable | Default | Purpose |
|---|---|---|
mongoDBURL required |
empty until setup | MongoDB connection string including the database name, e.g.
mongodb://127.0.0.1:27017/showcaseai or an Atlas mongodb+srv://… URL. See
how to get it. |
JWT_SECRET required |
empty until setup | Signs login tokens. At least 32 characters (the API refuses to start in production with a shorter
value). Generated by npm run setup. Set once — changing it logs everyone out. |
SETTINGS_ENCRYPTION_KEY required |
empty until setup | Encrypts the keys saved in Admin → Settings. Generated by npm run setup. Set once, never
change — if it changes, every saved key becomes unreadable and must be entered again. |
PORT |
5000 |
Port the API listens on. |
NODE_ENV |
development |
Set to production on your live server (enables stricter checks). |
Advanced (optional) — commented out in .env.example; leave them unless you need them. |
||
TRUST_PROXY |
empty | Empty = trust nginx on the same server. Set to 1 behind a cloud load balancer. |
SERVE_FRONTEND_DIST |
empty | e.g. ../frontend/dist — serve the built frontend from the API on one port. |
SALT_ROUNDS |
10 |
bcrypt cost for passwords (10–12). |
WEBHOOK_SECRET |
derived from JWT_SECRET |
Separate secret for provider webhooks. |
AWS_S3_FOLDER |
showcaseai-cc |
Folder inside the S3 bucket where every file is stored. Can also be set in Admin → Settings → Storage. |
ALLOW_HTTP_REMOTE_URLS |
false |
In production only https provider files are downloaded unless this is true. |
For compatibility, any Admin → Settings field can also be given in .env under
its key name (e.g. KIE_API_KEY=…). A value saved in Admin → Settings always
wins. You don't need this — the admin panel is the recommended way.
Frontend — frontend/.env
npm run setup (in backend/) creates this file from
frontend/.env.example if it does not exist yet, with VITE_API_URL set to your Backend URL + /api/. Without the
file, the frontend uses http://localhost:5000/api/.
| Variable | Purpose |
|---|---|
VITE_API_URL required |
Base URL of the backend API, ending with /api/ (e.g.
https://api.example.com/api/; locally http://localhost:5000/api/). |
VITE_APP_NAME |
Product name shown in the browser tab, page titles and footer. |
VITE_COOKIE_DOMAIN |
Only to share the login across subdomains (e.g. .example.com). Leave empty otherwise.
|
VITE_HOTJAR_ID |
Optional Hotjar analytics. Disabled when empty. |
VITE_TERMS_URL, VITE_PRIVACY_URL |
Links in the login-page footer. When empty, they open the built-in Terms of Service and Privacy
Policy pages (/terms-conditions and /privacy-policy) — replace their text
first, see Branding & Customisation. |
VITE_POWERED_BY_TEXT, VITE_POWERED_BY_URL |
"Powered by" credit in the login-page footer. Hidden when empty. |
VITE_SAMPLE_MEDIA_BASE |
Base URL that hosts the sample product images offered under "Try Samples" on the Create pages.
Defaults to https://d3orgd3vfbg4nb.cloudfront.net, a sample-image CDN hosted by the
vendor and used only to show example images. You can point it at your own host — see
Sample images. |
Sample images
The Create pages (Product Showcase, Product Avatar and AI Ads) offer four example product photos under
Try Samples. The browser loads them from VITE_SAMPLE_MEDIA_BASE, which by
default is the vendor-hosted sample-image CDN https://d3orgd3vfbg4nb.cloudfront.net.
It is used only to display these example images; nothing else in the app depends on it. If you prefer to
host the images yourself, upload four JPEG files to a bucket or web server you control using this path
convention, then set VITE_SAMPLE_MEDIA_BASE to its base URL and rebuild the
frontend:
<base>/assets/assets/sample-images/1.jpg … 4.jpg
<base>/assets/assets/sample-images/thumb/1.jpg … 4.jpg # smaller thumbnails of the same images
Backend: after editing backend/.env, restart the API (Ctrl+C then npm run dev, or
pm2 restart in production). Frontend: VITE_*
values are compiled into the build — run npm run build again after changing
them. Never put secrets in the frontend .env.
Keys & Credentials — Step by Step #
Every value ShowcaseAI needs, what it is for, where it goes and exactly where to get it. Values
marked .env go in backend/.env (most are written for you by npm run setup). Values marked Settings are entered in Admin → Settings
after you log in — click Save, then the Test button next to the key.
Overview
| Value | Where it goes | Needed for | Required? |
|---|---|---|---|
mongoDBURL | .env (setup asks) | Everything — the database | Yes |
JWT_SECRET, SETTINGS_ENCRYPTION_KEY | .env (setup generates) | Logins, encrypted settings | Yes (automatic) |
FRONTEND_URL, BACKEND_URL | Settings → Site & Branding (setup asks) | Email links, payments, AI callbacks | Yes |
VITE_API_URL | frontend/.env | Frontend talking to the API | Yes |
KIE_API_KEY | Settings → AI Providers | All image & video generation, all AI text | Yes for AI features |
AWS_* (S3) | Settings → Storage | Every upload and generated file | Yes for AI features |
SPEECHIFY_API_KEY | Settings → AI Providers | Voices, text-to-speech, voice cloning | For voice / avatar features |
NewPortAI_KEY | Settings → AI Providers | Talking avatars (lip-sync), Ad Localizer | For avatar features |
| Email (SMTP or Mandrill) | Settings → Email | Password reset & welcome emails | Recommended |
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET | Settings → Payments | Selling credit packs | Optional |
PIXABAY_KEY, UNSPLASH_KEY, PEXELS_KEY | Settings → Stock Media | Stock photo/video search | Optional |
1. MongoDB — mongoDBURL
The connection string of your database, including the database name. npm run setup asks for it and writes it to .env.
Option A — MongoDB on your own computer/server (free)
- Install MongoDB Community Server from
https://www.mongodb.com/try/download/community(on Windows tick "Install as a Service"; on Ubuntu follow the official apt instructions). - Use this connection string:
mongodb://127.0.0.1:27017/showcaseai
Option B — MongoDB Atlas (cloud, free tier available)
- Create an account at
https://www.mongodb.com/cloud/atlasand create a cluster (the free M0 tier is fine to start). - Database Access → Add New Database User → choose a username and a password (avoid
@ : / ?in the password, or URL-encode them). - Network Access → Add IP Address → add your server's public IP (use
0.0.0.0/0only for a quick test). - Database → Connect → Drivers → copy the
mongodb+srv://…string. - Replace
<password>with your password and add the database name before the?:
mongoDBURL=mongodb+srv://myuser:MyPassword123@cluster0.abcde.mongodb.net/showcaseai?retryWrites=true&w=majority
2. Secrets — JWT_SECRET and SETTINGS_ENCRYPTION_KEY
You don't need to do anything: npm run setup generates both and saves
them in .env (they are empty in .env.example). Create one by hand only on a server where you prepare the
.env yourself:
$ node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))"
Keep a private backup of both values (they live in backend/.env). Changing JWT_SECRET logs everyone out.
Changing SETTINGS_ENCRYPTION_KEY makes the keys saved in Admin → Settings
unreadable (you would have to enter them again). See Secrets – set once, never change.
3. Site URLs — FRONTEND_URL, BACKEND_URL, VITE_API_URL
| Value | On your computer | On a live server |
|---|---|---|
FRONTEND_URL (Admin → Settings) | http://localhost:4001 | https://app.example.com |
BACKEND_URL (Admin → Settings) | http://localhost:5000 * | https://api.example.com |
VITE_API_URL (frontend/.env) | http://localhost:5000/api/ | https://api.example.com/api/ |
* Kie.ai sends finished images and videos to BACKEND_URL, so it must
be reachable from the internet over https. On your own computer, generations stay "processing" unless you
open a tunnel: install ngrok (https://ngrok.com), run ngrok http 5000 and set BACKEND_URL (Admin → Settings →
Site & Branding) to the https://….ngrok-free.app address it shows.
4. Kie.ai — KIE_API_KEY
Powers all image and video generation (Seedance, Kling, Veo, Grok, Nano Banana, Seedream, Flux, GPT Image…) and all AI text (scripts, ad copy, translations).
- Sign up at
https://kie.ai. - Open the API Key page in your Kie dashboard and create/copy your key.
- Add credit to your Kie account (Billing) — generation fails without a balance.
- Admin → Settings → AI Providers → paste into Kie API Key → Save → Test. The test shows your remaining Kie credit.
5. AWS S3 storage — AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_S3_BUCKET
Every uploaded and generated file is stored in your S3 bucket. The files must be publicly readable because the AI providers download them by URL.
- Sign in to
https://console.aws.amazon.com→ S3 → Create bucket. Pick a name (e.g.showcaseai-media) and a region (e.g.us-east-1). These areAWS_S3_BUCKETandAWS_REGION. - In the bucket → Permissions → Block public access → Edit → untick Block all public access → Save.
- Same tab → Bucket policy → paste the policy below (replace the bucket name) → Save.
- Same tab → Cross-origin resource sharing (CORS) → paste the CORS rules below (needed for video thumbnails in the browser).
- IAM → Users → Create user (e.g.
showcaseai) → Add permissions → Create inline policy → JSON → paste the IAM policy below. - Open the user → Security credentials → Create access key → "Application running outside AWS" → copy the Access key ID and Secret access key (the secret is shown only once).
- Admin → Settings → Storage (AWS S3) → fill Region, Access Key ID, Secret Access Key and Bucket Name → Save → Test.
Bucket policy (public read):
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "PublicRead",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::showcaseai-media/*"
}]
}
CORS:
[{
"AllowedOrigins": ["https://app.example.com", "http://localhost:4001"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3600
}]
IAM policy (least privilege):
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::showcaseai-media/*" },
{ "Effect": "Allow", "Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::showcaseai-media" }
]
}
Optional: AWS_S3_PUBLIC_URL = a CloudFront/CDN domain in front of the bucket
(e.g. https://media.example.com). AWS_S3_ACL = public-read
only if your bucket uses ACLs instead of a bucket policy (then also add s3:PutObjectAcl to the
IAM policy). Otherwise leave both empty. Every file is stored under one folder in the bucket
(AWS_S3_FOLDER, default showcaseai-cc), so a bucket shared with another
project stays tidy.
6. Speechify — SPEECHIFY_API_KEY
Powers the voice library, text-to-speech, voice-overs and voice cloning.
- Sign up for the Speechify API at
https://console.sws.speechify.com. - Open API Keys → create a key → copy it.
- Admin → Settings → AI Providers → Speechify API Key → Save → Test. The test also imports the Speechify voice library ("Imported N Speechify voices"). It is also imported automatically the first time someone opens the voice picker.
7. NewPortAI — NewPortAI_KEY
Powers lip-sync for talking-avatar videos and the Ad Localizer.
- Sign up at
https://www.newportai.com. - Open your dashboard → API Key → copy it, and add credit to your account.
- Admin → Settings → AI Providers → NewPortAI Key → Save → Test.
8. Email — SMTP or Mandrill
Used for password-reset links and the optional welcome email when an admin adds a user. Choose one provider with the Send Mail Via switch (Admin → Settings → Email / Notifications).
SMTP (any email provider)
| Provider | SMTP_HOST | SMTP_PORT | SMTP_USERNAME / SMTP_PASSWORD |
|---|---|---|---|
| Gmail / Google Workspace | smtp.gmail.com | 587 | Your address / an App Password (Google Account → Security → 2-Step Verification → App passwords) |
| SendGrid | smtp.sendgrid.net | 587 | apikey / your SendGrid API key |
| Amazon SES | email-smtp.<region>.amazonaws.com | 587 | SMTP credentials from SES → SMTP settings |
| Mailgun | smtp.mailgun.org | 587 | SMTP login / password from Mailgun → Domain settings |
| Your hosting mailbox | from your host | 465 (SSL) or 587 | Mailbox address / password |
Then set Send Mail Via = SMTP, FROM_EMAIL (an address your provider
allows you to send from, e.g. no-reply@example.com) and SUPPORT_EMAIL (shown in emails). Port 465 uses SSL automatically; 587 uses STARTTLS.
Mandrill (Mailchimp Transactional)
- In Mailchimp open Transactional (Mandrill) → Settings → Add API key → copy it.
- Under Domains add and verify your sending domain (DKIM/SPF records).
- Admin → Settings → Email: Send Mail Via = Mandrill, paste Mandrill API Key, set From Email on the verified domain. If Mandrill fails and SMTP is also filled in, SMTP is used as a fallback.
9. Stripe — STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET (optional)
Needed only if you sell credit packs on the Buy Credits page.
- Sign in at
https://dashboard.stripe.com(use Test mode first). - Developers → API keys → reveal and copy the Secret key (
sk_test_…/sk_live_…). - Developers → Webhooks → Add endpoint → URL
https://api.example.com/api/payment/stripe/webhook→ eventscheckout.session.completedandcheckout.session.async_payment_succeeded→ Add. - Open the endpoint → Signing secret → reveal and copy (
whsec_…). - Admin → Settings → Payments (Stripe) → paste both → Save → Test. Then check your packs in Admin → Credit Packs.
Testing on your own computer: install the Stripe CLI and run stripe listen
--forward-to localhost:5000/api/payment/stripe/webhook — it prints a whsec_… secret to
use while it runs. Test card: 4242 4242 4242 4242, any future date, any CVC.
10. Stock media — PIXABAY_KEY, UNSPLASH_KEY, PEXELS_KEY (optional, free)
| Key | Where to get it |
|---|---|
PIXABAY_KEY | Log in at https://pixabay.com, open https://pixabay.com/api/docs/ — your key is shown under "Parameters → key". |
UNSPLASH_KEY | https://unsplash.com/oauth/applications → New Application → copy the Access Key. |
PEXELS_KEY | https://www.pexels.com/api/ → Your API Key → copy it. |
Enter them in Admin → Settings → Stock Media. Any provider without a key is simply not searchable.
11. Credit prices and limits
Nothing to obtain — just decide your prices. Admin → Settings → Credit Prices sets what every action costs (image, avatar, talking video, showcase video, AI Ads, voice clone, voice-over, Ad Localizer, AI text); the app shows users the current price. Admin → Settings → Limits & Security sets the login session length, rate limits, maximum upload size and when held credits are returned. See the full list with defaults.
12. Complete example files
backend/.env — on your computer (written by npm run
setup)
mongoDBURL=mongodb://127.0.0.1:27017/showcaseai JWT_SECRET=<generated by npm run setup> SETTINGS_ENCRYPTION_KEY=<generated by npm run setup> PORT=5000 NODE_ENV=development
backend/.env — on a live server
mongoDBURL=mongodb+srv://myuser:MyPassword123@cluster0.abcde.mongodb.net/showcaseai?retryWrites=true&w=majority JWT_SECRET=<generated by npm run setup> SETTINGS_ENCRYPTION_KEY=<generated by npm run setup> PORT=5000 NODE_ENV=production
That's all — the site URLs, keys, email, payments, prices and limits are entered in Admin → Settings.
frontend/.env (rebuild the frontend after changing it)
VITE_API_URL=https://api.example.com/api/ VITE_APP_NAME=ShowcaseAI VITE_TERMS_URL=https://example.com/terms VITE_PRIVACY_URL=https://example.com/privacy
Finished? Run npm run check in backend — it
shows a ✔ next to every configured setting and warns about anything missing (it never prints the values).
Admin → Settings #
One screen in the admin panel for every external key and site option. No file editing and no restart needed.
How it works
- Values are saved in MongoDB (collection
sitedatas, keyapp_credentials). Secret values are encrypted with AES-256-GCM usingSETTINGS_ENCRYPTION_KEY(orJWT_SECRETif that is not set). - Precedence: value saved in Admin → Settings → otherwise the variable with the same name in
backend/.env→ otherwise empty. - Changes apply immediately.
- Secrets are never sent back to the browser; they are shown as
••••••••. Leaving a secret field empty keeps the stored value. - Keep
SETTINGS_ENCRYPTION_KEYstable. If it changes, the saved secrets can no longer be decrypted and must be re-entered.
Secrets – set once, never change. Save provider keys only after npm run setup has run. If SETTINGS_ENCRYPTION_KEY (in backend/.env) ever changes, all keys saved here become unreadable and show as not configured. Restore the original value and restart to get them back; otherwise you would have to re-enter every key. Details.
Groups and fields
| Field | Default | What it does |
|---|---|---|
| Site & Branding — Public addresses of your installation and how your brand appears in emails. | ||
Frontend URLFRONTEND_URL |
— | Where users open the app, e.g. https://app.example.com (used in email links and payment return pages). |
Backend URLBACKEND_URL |
— | Public https URL of this API without /api, e.g. https://api.example.com. AI providers send finished results here. |
App NameAPP_NAME |
ShowcaseAI |
Shown in email subjects and as the email sender name. |
Support URLSUPPORT_URL |
— | Help/support link shown to users, e.g. https://support.example.com |
Email Logo URLEMAIL_LOGO_URL |
— | Optional. Image shown at the top of emails. Default: <Backend URL>/logo.png |
| AI Providers — Keys for the AI services that power generation. | ||
Kie API KeyKIE_API_KEY secret, has Test button |
— | Powers image, video, and all text/translation. |
Speechify API KeySPEECHIFY_API_KEY secret, has Test button |
— | Powers the voice library, text-to-speech and voice cloning. Test also imports the voice library. |
NewPortAI KeyNewPortAI_KEY secret, has Test button |
— | Powers lip-sync for talking videos and the Ad Localizer. |
| Storage (AWS S3) — Where uploaded and generated media is stored. | ||
AWS RegionAWS_REGION |
— | e.g. us-east-1 |
AWS Access Key IDAWS_ACCESS_KEY_ID secret |
— | |
AWS Secret Access KeyAWS_SECRET_ACCESS_KEY secret, has Test button |
— | |
S3 Bucket NameAWS_S3_BUCKET |
— | |
S3 Public / CDN URLAWS_S3_PUBLIC_URL |
— | Optional. Defaults to the bucket's S3 URL. |
S3 FolderAWS_S3_FOLDER |
showcaseai-cc |
Top-level folder inside the bucket where every file is stored (uploads, generated media and the sample assets under assets/). Change it if the bucket is shared with another project. Files already uploaded stay where they are. |
S3 Object ACLAWS_S3_ACL |
— | Optional. Set to public-read only if your bucket uses ACLs; leave empty when a bucket policy makes objects public. |
| Email / Notifications — How password-reset and welcome emails are sent. | ||
Send Mail ViaSEND_MAIL_FROM |
Mandrill / SMTP | Choose which provider sends emails. SMTP is used as an automatic fallback if the primary send fails. |
From EmailFROM_EMAIL |
— | The address emails are sent from, e.g. no-reply@example.com |
Support EmailSUPPORT_EMAIL |
— | Shown to users in emails. |
SMTP HostSMTP_HOST |
— | e.g. smtp.gmail.com |
SMTP PortSMTP_PORT |
— | 587 (STARTTLS) or 465 (SSL) |
SMTP UsernameSMTP_USERNAME |
— | |
SMTP PasswordSMTP_PASSWORD secret, has Test button |
— | |
Mandrill API KeyMANDRILL_API_KEY secret |
— | Only needed when sending via Mandrill. |
| Payments (Stripe) — Stripe keys for selling credit packs (Admin → Credit Packs). | ||
Stripe Secret KeySTRIPE_SECRET_KEY secret, has Test button |
— | Test key starts with sk_test_. |
Stripe Webhook SecretSTRIPE_WEBHOOK_SECRET secret |
— | Signing secret (whsec_…) of the webhook <Backend URL>/api/payment/stripe/webhook |
| Credit Prices — How many credits each action costs. Leave a field empty to use the default shown. 0 = free. Creator Studio prices are set per model. | ||
Image generationCREDITS_IMAGE |
0.15 |
Image Studio, without a reference image. |
Image generation with a reference imageCREDITS_IMAGE_WITH_REFERENCE |
0.25 |
Image Studio with a reference, product shoot images, avatar + product images. |
Create avatar from an imageCREDITS_AVATAR_FROM_IMAGE |
5 |
Turns an uploaded photo into a moving avatar. |
Create avatar from a videoCREDITS_AVATAR_FROM_VIDEO |
2 |
Uses an uploaded video as an avatar. |
Talking avatar video (lip-sync)CREDITS_TALKING_AVATAR |
2 |
Per talking-avatar video. |
Product / fashion avatar videoCREDITS_PRODUCT_AVATAR |
5 |
Avatar presenting or wearing a product. |
Product / fashion showcase video (10 s)CREDITS_SHOWCASE_VIDEO_10S |
3 |
Product shoot and fashion show videos, 10 seconds. |
Product / fashion showcase video (5 s)CREDITS_SHOWCASE_VIDEO_5S |
1.5 |
Product shoot and fashion show videos, 5 seconds. |
AI Ads videoCREDITS_AI_ADS |
3 |
Default AI Ads model. |
AI Ads video (Veo)CREDITS_AI_ADS_VEO |
5 |
AI Ads with the Veo model. |
Voice cloneCREDITS_VOICE_CLONE |
5 |
Per cloned voice. |
Voice-over (per 500 characters)CREDITS_TTS_PER_500_CHARS |
0.1 |
Saving a text-to-speech voice-over. |
Voice previewTTS_PREVIEW_CREDITS |
0 |
Per text-to-speech preview (previews are rate-limited). |
Ad Localizer (per language)CREDITS_AD_LOCALIZER_PER_LANGUAGE |
2 |
Per translated video, plus the AI text price for the translation. |
AI text (per request)LLM_TEXT_CREDITS |
0.1 |
Ad scripts, ad copy, sales copy, prompt ideas and translations. |
| Limits & Security — Session length, rate limits and file size limits. Changes apply immediately. | ||
Login session length (days)SESSION_DAYS |
7 |
Validity of a login token, 1–90 days. The browser keeps the sign-in for at most 7 days, so values above 7 do not extend it. Applies to new logins. |
Failed logins allowed per 15 minutesLOGIN_RATE_LIMIT |
10 |
Per IP address. After that, logins are blocked for 15 minutes. |
API requests per 5 minutesAPI_RATE_LIMIT |
1000 |
Per IP address, across the whole API. |
Max upload size (MB)MAX_UPLOAD_MB |
100 |
Largest file a user can upload. Also raise client_max_body_size in nginx. |
Max downloaded result size (MB)MAX_REMOTE_FILE_MB |
200 |
Largest generated file the API will download from a provider. |
Return held credits after (minutes)RESERVATION_TTL_MIN |
120 |
If a provider never answers, reserved credits are returned after this time. |
| Stock Media — Optional keys for the stock image/video search inside the media picker. | ||
Pixabay API KeyPIXABAY_KEY secret |
— | Free key from pixabay.com/api/docs |
Unsplash Access KeyUNSPLASH_KEY secret |
— | Free key from unsplash.com/oauth/applications |
Pexels API KeyPEXELS_KEY secret |
— | Free key from pexels.com/api |
URL fields accept a trailing slash or not. Number fields are validated (an invalid value is rejected with a message); an empty field uses the default shown. Reset next to a field removes the saved value; Remove deletes a saved secret.
Where to get each key
Step-by-step instructions for every key — MongoDB, Kie.ai, AWS S3 (bucket policy, CORS and IAM policy), Speechify, NewPortAI, SMTP/Mandrill, Stripe (including the webhook) and the stock media providers — are in Keys & Credentials — Step by Step.
Without the webhook secret, Stripe webhook calls are refused. The payment-success page still verifies the payment directly with Stripe, so a buyer who returns to your site still receives credits — but configure the webhook so credits are also granted when the buyer closes the browser before returning.
Email behaviour
If Mandrill fails and SMTP is configured, SMTP is used as a fallback. If nothing is configured, emails are skipped and a warning ("Email not sent") is logged — the API keeps running.
Production Deployment #
A typical setup is one Linux server running the API under PM2 behind nginx, with the built frontend served as static files and HTTPS from Let's Encrypt.
1 · Build the frontend
$ cd frontend # set VITE_API_URL=https://api.example.com/api/ in .env first (copy .env.example to .env if it is missing) $ npm ci $ npm run build # output in dist/
Upload the contents of dist/ to your web root. The web server must send every
unknown path to index.html (single-page-app fallback), otherwise refreshing a
page such as /dashboard returns 404.
2 · Install and start the API
$ cd backend $ cp .env.example .env # then set NODE_ENV=production in .env $ npm ci --omit=dev $ npm run setup -- --yes --no-demo --mongo "<your MongoDB URL>" \ --admin-email you@example.com --admin-password "<a strong password>" \ --frontend-url https://app.example.com --backend-url https://api.example.com # ^ first install only. Skip it if the database is already set up. $ npm install -g pm2 $ pm2 start deploy/ecosystem.config.cjs $ pm2 save $ pm2 startup # follow the printed command so PM2 starts on boot
The package contains no .env file. On a live server, copy backend/.env.example to backend/.env and set NODE_ENV=production in it before you run npm run
setup. Setup keeps the existing file and fills in mongoDBURL and the
secrets; with NODE_ENV=production it never creates the demo user. Add --mongo, --backend-url, --frontend-url and --admin-email / --admin-password as needed (see the options); without a password,
setup prints a generated admin password once — save it.
The ecosystem file sets the working directory to backend and NODE_ENV=production. Useful commands: pm2 logs, pm2 restart all, pm2 status.
Run exactly one instance. Rate limits and provider polling are kept in memory, so do not use PM2 cluster mode or several instances.
3 · Configure nginx
Start from deploy/nginx.conf.example and replace the domain names. It contains:
- An app domain (e.g.
app.example.com) servingdist/withtry_files $uri $uri/index.html /index.html;for the SPA fallback. - An API domain (e.g.
api.example.com), or an/apipath, proxying tohttp://127.0.0.1:5000withclient_max_body_size 100m, 300-second proxy timeouts and theX-Forwarded-For/X-Forwarded-Protoheaders.
Then enable HTTPS with Let's Encrypt, for example:
$ sudo certbot --nginx -d app.example.com -d api.example.com
Single-server option
Instead of a separate web root, set SERVE_FRONTEND_DIST=../frontend/dist in
backend/.env. The API then serves the frontend too, on the same domain. Build
the frontend with VITE_API_URL=https://yourdomain.com/api/ and point nginx at
the API only.
When SERVE_FRONTEND_DIST is set and dist/index.html exists, the API serves, in this order:
/api/* (the API), the built static files, and finally
index.html for every other non-API path (so React Router URLs survive a refresh). If dist/index.html is missing the API logs a
warning and serves no frontend — run npm run build in frontend/ first.
4 · Final configuration
- Set
FRONTEND_URLandBACKEND_URLto your real https domains. - Configure the Stripe webhook (see Admin → Settings).
- Use your own admin login: change the password in Profile and the email in MongoDB Compass (see Changing the admin login).
- Rotate a secret only if it has been exposed, and only
JWT_SECRET(generate a new value as shown in Secrets, paste it into.envand restart — everyone is logged out). Never changeSETTINGS_ENCRYPTION_KEYon a live site (see Secrets – set once, never change); rotate provider keys in their own dashboards instead. - Back up MongoDB regularly, and keep a copy of
backend/.envsomewhere safe.
Go-live checklist
| Check | How |
|---|---|
NODE_ENV=production |
In backend/.env (PM2 ecosystem also sets it). |
Strong, unique JWT_SECRET and SETTINGS_ENCRYPTION_KEY |
Generated by npm run setup; never reuse values from another site. Keep a backup of backend/.env and never change SETTINGS_ENCRYPTION_KEY afterwards. |
| HTTPS on every domain | certbot / your host's certificate. |
Public https BACKEND_URL |
Admin → Settings → Site & Branding; npm run check warns about localhost. |
| CORS restricted | FRONTEND_URL set to the exact frontend origin. |
| All provider keys pass their Test button | Admin → Settings. |
| S3 bucket publicly readable, IAM user least-privilege | Bucket policy + IAM policy above. |
| Stripe live key + webhook | sk_live_…, live webhook endpoint and its whsec_…. |
| Email configured | Send yourself a password reset. |
| Model prices reviewed | Adjust pricingMatrix / costPerSecond in aimodelcatalogs and
the credit-pack prices. |
| Admin login is your own | Password in Profile; email in MongoDB Compass (how). |
| MongoDB secured | Authentication enabled, network access restricted, backups scheduled. |
| Installation check passes | npm run check. |
Branding & Customisation #
Rename and rebrand the product without touching application code.
| What | Where |
|---|---|
| Product name in the browser, titles and footer | VITE_APP_NAME in frontend/.env (rebuild afterwards). |
| Terms / privacy links, "Powered by" credit, sample images | VITE_TERMS_URL, VITE_PRIVACY_URL, VITE_POWERED_BY_TEXT,
VITE_POWERED_BY_URL, VITE_SAMPLE_MEDIA_BASE — see
Configuration. |
| Privacy Policy and Terms of Service pages | frontend/src/pages/legal/PrivacyPolicy.jsx and TermsConditions.jsx
(rebuild afterwards) — see the note below. |
| Name in email subjects and sender name | Admin → Settings → Site & Branding → APP_NAME. |
| Email content | backend/src/emailTemplates/ (resetPassword.html,
userCreated.html). |
| Logo in emails | Replace backend/public/logo.png (served at <BACKEND_URL>/logo.png).
|
| Favicon and app logo | frontend/public/assets/images/ (rebuild afterwards). |
Replace the legal text before going live. The built-in Privacy Policy
(/privacy-policy) and Terms of Service
(/terms-conditions) pages are generic templates. They contain the contact
address support@showcaseai.app and a "Last Updated" date. Replace them with
your own company details, contact address and legal text (edit the two files in
frontend/src/pages/legal/ and rebuild), or set
VITE_TERMS_URL and VITE_PRIVACY_URL to your own
pages.
Email template variables
The templates can use these placeholders, which are replaced when an email is sent:
| Variable | Value |
|---|---|
{{app_name}} |
The APP_NAME setting. |
{{logo_url}} |
URL of the email logo. |
{{name}} |
Recipient's name. |
{{email}} |
Recipient's email address (welcome email). |
{{reset_url}} |
Password-reset link (reset email). |
{{set_password_url}} |
"Set your password" link (welcome email, valid 48 hours). |
{{login_url}} |
Login page link. |
{{support_email}} |
The SUPPORT_EMAIL setting. |
{{frontend_url}} |
The FRONTEND_URL setting. |
Authentication & Users #
JWT authentication with email and password — there is no social/OAuth login. Visitors can create
their own account on the sign-up page (/register), and admins can create accounts in
the admin panel. The first admin is created by the setup script (see Admin Account &
Starter Data).
Creating users
- Self sign-up:
/register(name, email, password). New accounts get theuserrole and start with no credits until an admin adds some or they buy a credit pack. Sign-ups are limited to 5 per hour per IP address. - An admin opens Admin → Users → Add User and enters a name, email and password, plus an optional
starting credit balance. The server always creates the account as a regular user (
userrole,activestatus). - Deleted accounts: sign-up with the email of a deleted account is refused ("This email can't be used to register. Please contact the site administrator."). Only an admin can restore it, in Admin → Users → Add User with the same email; the account comes back with its previous data and credits ("Deleted account restored with its previous data").
- With the Send Email checkbox ticked, the user receives a welcome email containing a "set your password" link valid for 48 hours (the email never contains a plaintext password). Email must be configured for this.
Login
- User submits email + password to POST
/auth/login. - Backend finds the user, verifies the password with bcrypt, and rejects deleted and inactive accounts.
Failed attempts are rate-limited (
LOGIN_RATE_LIMIT, default 10 per 15 minutes per IP). - A JWT (HS256) is signed with
JWT_SECRET, valid for the Login session length setting (SESSION_DAYS, 1–90 days, default 7), and returned with the user object (password removed). The browser keeps theauth_tokencookie for at most 7 days, so a user stays signed in for the shorter of the two. - Frontend stores the token in the
auth_tokencookie (SameSite=Lax, Secure on https), then redirects:admin→/admin/dashboard, others →/dashboard.
Token handling
- Every API call sends
Authorization: Bearer <token>(from the cookie). MiddlewareModule.checkAuthverifies the signature (algorithm pinned to HS256), loads the user, checks the user is still active and not deleted, and checks the token version. It setsreq.userplus request context (source,storeId,domainfrom headers). Failures return HTTP 401{"status":"error","errorCode":"InvalidToken"}.MiddlewareModule.checkAdminruns after it on every/admin/*route and returns HTTP 403{"errorCode":"Forbidden"}for non-admins.- Session revocation: each user has a token version. Logout (POST
/auth/logout), a password change (by the user or by an admin), and an admin deactivating or deleting a user all invalidate every existing token of that user immediately. - On app load,
ProtectedLayoutcalls GET/auth/get-auth-userto re-hydrate the session. Any 401 response clears the cookie and redirects to/login.
Password reset & password changes
- POST
/auth/forgot-passwordemails a reset link with a random single-use token. Only its SHA-256 hash is stored; it expires after 1 hour. The response is identical whether or not the email exists (no account enumeration). - POST
/auth/reset-passwordtakes{ token, password }, sets the new hashed password and consumes the token. - Changing the password on the profile page (
/update-profile) requires the current password. Minimum password length is 8 characters. - Forgot-password and reset-password requests share a limit of 5 per hour per IP. Sign-up has its own limit of 20 per hour per IP.
Roles & special flags
- role —
useroradmin. Enforced on the server for every/admin/*route; the frontend also redirects non-admins away from/admin/*pages. There is exactly one admin account (created by setup); roles cannot be changed afterwards. - isSpecialUser — a "VIP" flag that grants effectively unlimited credits (returns a 10,000 balance and never deducts).
- status (active/inactive), isDeleted (soft delete), userSource (where the account
originated:
webfor sign-ups,adminfor accounts added in Admin → Users,setuporseedfor the accounts created bynpm run setup). A soft-deleted email cannot register again; only an admin can restore it from Admin → Users → Add User. - An admin cannot deactivate or delete their own account, and the last admin cannot be removed.
Frontend access control
checkAccess() and applyAccessControl() in utils/Helper.js are the "pro feature" gate — currently set to unlock
everything. They mark all menu items as available and remove "PRO/upgrade" badges.
The Credit System #
A ledger-based wallet. Every paid action costs credits; the balance is topped up by admin grants or Stripe purchases. All prices are calculated on the server — never taken from the browser.
The building blocks
| Model / helper | Role |
|---|---|
Credit (collection credits) |
A batch of credits added (creditsAssigned, creditsUsed,
type: creditPurchased for Stripe purchases, assignbyadmin for
admin grants and demo data). |
CreditReservation |
A temporary hold while a paid provider job runs, so credits cannot be double-spent. |
CreditUsed (collection credituseds) |
A log of every deduction — the usage history. |
CreditLock |
A short per-user lock so that balance checks and deductions for one user happen one at a time. |
CreditHelper |
The functions all features call: reserve, attachTask,
settle, release, chargeNow, getCreditDetails,
plus the server-side price functions priceForStudio and priceForTts. |
Available balance = active credits (assigned) − used − currently reserved.
Admins assign credits when creating or editing a user (Admin → Users). Users see their balance in the sidebar and their history on the billing page.
How credits are spent: reserve → settle on success
Users are only charged for generations that succeed.
- Reserve — before a paid provider is called,
CreditHelper.reservechecks the balance and creates aCreditReservation(statusRESERVED) inside the per-user lock. This lowers the available balance immediately, so parallel requests cannot overspend. If the provider rejects the job, the reservation is released at once. - Settle — when the provider reports success (Kie webhook, or NewPortAI polling),
CreditHelper.settleconsumes the reservation and records the charge. Duplicate callbacks find no reservation, so a job can never be charged twice. - Release — on failure the reservation is deleted with no charge.
- Timeout — a background sweeper releases reservations whose provider never answered within
RESERVATION_TTL_MIN(default 120 minutes) and marks the related media as failed ("Generation timed out. Your credits have been returned.").
Instant actions (AI text, saved text-to-speech, direct video-to-avatar upload) use CreditHelper.chargeNow, which checks and deducts in one locked step.
Where credits actually leave the account
| Flow | Reserved in | Charged in |
|---|---|---|
| Kie image / video / avatar / AI-ads / Creator Studio | kieLib (before the Kie request) |
kieController on /api/kie/webhook (success) |
| NewPortAI lip-sync (talking avatar, Ad Localizer) | render.js |
newportai poll (status completed) |
| Voice clone | ttsController.addUserVoice |
Same request, after Speechify accepts the clone |
| Video-to-avatar (direct upload) | — | Immediate in videoController.addUserAvatar |
| Text-to-speech (saved) | — | Immediate in ttsController.synthesizeText |
| AI text tools | — (balance checked first) | After a successful response (LLM_TEXT_CREDITS) |
| Credit purchase (grant, not spend) | — | paymentController creates a Credit row once Stripe confirms the payment
|
Costs
All prices are set in Admin → Settings → Credit Prices (empty = default, 0 = free) and the app shows
the current price to users. Creator Studio prices come from each model in the aimodelcatalogs collection.
| Action | Setting | Default credits |
|---|---|---|
| Image generation Image Studio, without a reference image. |
CREDITS_IMAGE |
0.15 |
| Image generation with a reference image Image Studio with a reference, product shoot images, avatar + product images. |
CREDITS_IMAGE_WITH_REFERENCE |
0.25 |
| Create avatar from an image Turns an uploaded photo into a moving avatar. |
CREDITS_AVATAR_FROM_IMAGE |
5 |
| Create avatar from a video Uses an uploaded video as an avatar. |
CREDITS_AVATAR_FROM_VIDEO |
2 |
| Talking avatar video (lip-sync) Per talking-avatar video. |
CREDITS_TALKING_AVATAR |
2 |
| Product / fashion avatar video Avatar presenting or wearing a product. |
CREDITS_PRODUCT_AVATAR |
5 |
| Product / fashion showcase video (10 s) Product shoot and fashion show videos, 10 seconds. |
CREDITS_SHOWCASE_VIDEO_10S |
3 |
| Product / fashion showcase video (5 s) Product shoot and fashion show videos, 5 seconds. |
CREDITS_SHOWCASE_VIDEO_5S |
1.5 |
| AI Ads video Default AI Ads model. |
CREDITS_AI_ADS |
3 |
| AI Ads video (Veo) AI Ads with the Veo model. |
CREDITS_AI_ADS_VEO |
5 |
| Voice clone Per cloned voice. |
CREDITS_VOICE_CLONE |
5 |
| Voice-over (per 500 characters) Saving a text-to-speech voice-over. |
CREDITS_TTS_PER_500_CHARS |
0.1 |
| Voice preview Per text-to-speech preview (previews are rate-limited). |
TTS_PREVIEW_CREDITS |
0 |
| Ad Localizer (per language) Per translated video, plus the AI text price for the translation. |
CREDITS_AD_LOCALIZER_PER_LANGUAGE |
2 |
| AI text (per request) Ad scripts, ad copy, sales copy, prompt ideas and translations. |
LLM_TEXT_CREDITS |
0.1 |
| Creator Studio | model catalog | pricingMatrix per resolution/duration, or costPerSecond × duration |
Operational note: Kie-based charges are finalised by the /api/kie/webhook callback, which must reach your server at BACKEND_URL (public https). If it cannot, jobs stay "Processing" until the sweeper
returns the credits after RESERVATION_TTL_MIN. NewPortAI lip-sync completes via
in-process polling and works without a public webhook.
AI Providers & Storage #
All AI work is delegated to three external services; media is stored on Amazon S3. Keys for all of them are entered in Admin → Settings.
| Service | Powers | Code |
|---|---|---|
| Kie | Image generation, video generation, and all LLM text (ad scripts, copy, translation) via its OpenAI-compatible Gemini endpoint | lib/kieLib.js, helpers/LlmHelper.js |
| Speechify | Text-to-speech (multilingual) and voice cloning | lib/tts.js |
| NewPortAI | Lip-sync — syncs an avatar's mouth to an audio track | lib/newportai.js |
| Amazon S3 | Stores all generated & uploaded media (optionally via CloudFront CDN). Files must be publicly readable because the providers download them. | helpers/UploadHelper.js |
| Pixabay / Unsplash / Pexels | Optional stock media search | userMediaController (/get-thirdParty-media) |
| MongoDB | All application data | Mongoose models |
The Ad Localizer is the clearest example of these working together: Kie translates the script → Speechify re-voices it in the target language → NewPortAI re-syncs the avatar's lips — reusing infrastructure the app already had.
Async Generation Flow #
Videos and images don't finish instantly. Here's the full lifecycle of a slow job.
Kie image / video
- The controller calculates the price on the server and calls
kieLib.generateImage/generateVideo. kieLibfirst reserves the credits (CreditHelper.reserve). If the balance is too low, nothing is sent to Kie.- The job is sent to Kie with the callback URL
<BACKEND_URL>/api/kie/webhook?token=…. If Kie rejects it, the reservation is released; if Kie accepts it, the Kie task id is attached to the reservation, aPredictionReplicatetracking row is created, and the task id is stored on the media record'srenderData.renderServerId. Status =processing. - Kie calls back to POST
/api/kie/webhook(kieController.ipn). Calls without a validtokenare rejected with 401, and each task is claimed exactly once (duplicate or late callbacks are ignored):- Success →
CreditHelper.settlecharges the reserved credits, the result is re-uploaded to S3 and the record is markedcompleted. - Failure → the record is marked
failedand the reservation is released (no charge).
- Success →
- For videos, completion is finalised through
commonComplete.js(generalVideoComplete/avatarComplete). An avatar clip additionally feeds a follow-up NewPortAI lip-sync job. - If no callback ever arrives, the reservation sweeper returns the credits after
RESERVATION_TTL_MINand marks the record failed.
NewPortAI lip-sync
render.jsreserves the talking-avatar price (CREDITS_TALKING_AVATAR, default 2) and starts a lip-sync task; the Ad Localizer does the same with its own per-language price.newportai.jspolls the task every 10 seconds (up to 15 minutes). On completion it uploads the video, marks theUserVideosrecordcompleted, and settles the reserved credits.
On the frontend, creation pages poll the video/image list (e.g. useAvatarVideoPolling) so a card visibly moves from Processing →
Completed/Failed in Projects.
Feature Guide — Creative Studios #
Image Studio
Core/imagestudio- What it does
- Text-to-image generation, optionally guided by a reference image and a Brand Kit.
- Endpoints
- POST
/generate-images,/get-generated-images-list,/add-manual-image - Provider / model
- Kie (
z-image, ornano-bananawhen a reference image is supplied) - Models
ImageGeneration- Cost
- 0.15 credits (0.25 with reference image)
How the user uses it
- Open Image Studio from the sidebar.
- (Optional) Upload a reference photo if you want the image based on it.
- Type a description of the image you want in the prompt box.
- (Optional) Choose a Brand Kit to apply your brand colours & style.
- Click Generate — the image appears in the gallery on the right within seconds.
- Hover any image to download, edit/regenerate, or delete it.
Video Studio
Core/videostudio · /create-*Four video types, each with its own builder:
| Type | Route | Endpoint | Cost |
|---|---|---|---|
| Product Showcase | /create-product-showcase-videos |
generate-product-shoot-images → generate-video-prompt →
generate-video
|
3 |
| Product Avatar | /create-product-avatar-videos |
generate-avatar |
5 |
| Talking Avatar | /create-avatar-videos |
synthesize-text → generate-user-avatar-video |
2 |
| Fashion / Try-on | /create-tryon-videos |
combine-avatar-product |
3 |
- Providers
- Kie (video), Speechify (voice), NewPortAI (lip-sync for talking avatars)
- Models
UserVideos,UserAvatar,VideoAvatar,ProductAvatar
How the user uses it
- Open Video Studio and choose the video type (Product Showcase, Product Avatar, Talking Avatar, or Fashion).
- Select an avatar/presenter — or a product image, depending on the type.
- Write the script, or click AI Writer to generate one (optionally applying a Brand Kit).
- Choose a voice and preview the audio.
- Click Generate. The video renders in the background (a few minutes).
- Open Projects to watch it move from Processing → Completed, then download it.
AI Ads Video
Core/create-ai-ads- What it does
- Generates short ad videos using cutting-edge models (SORA-style, Veo, Grok).
- Endpoints
generate-video-prompt→generate-ai-ads-video- Cost
- 3 credits (5 for Veo)
How the user uses it
- On the Dashboard, click the AI Ads Videos card.
- Upload a product image or type your ad idea.
- Pick a model tab (SORA / Veo / Grok).
- Generate the prompt, then click generate to create the video.
- Find the finished ad in Projects.
Text to Speech
Core/voiceover-studio- What it does
- Turns text into a natural voiceover; supports SSML-style emotion/pause tags; can save the audio to your media library.
- Endpoints
synthesize-text,get-premium-voices-list,get-user-uploads- Provider
- Speechify (
simba-multilingual) - Cost
- Charged when saved: 0.10 credit per started 500 characters, calculated on the server from the script.
Previews cost
TTS_PREVIEW_CREDITS(default 0) and are limited to 20 per minute.
How the user uses it
- Open Text to Speech.
- Pick a voice (filter by language, gender, or style).
- Type your text; optionally add emotion/pause tags for expression.
- Click Generate Audio to preview it.
- Save it to keep it in your media library (this uses credits).
Voice Clone
Core/voicestudio- What it does
- Creates a custom voice from an uploaded sample, then selectable anywhere voices are picked.
- Endpoints
file-to-upload→add-user-voice; list viaget-user-voices-list- Provider
- Speechify
- Cost
- 5 credits (reserved first, charged once Speechify accepts the clone). A cloned voice can only be used by the user who created it.
How the user uses it
- Open Voice Clone.
- Upload or record a clear sample of the voice.
- Give it a name and click create (5 credits).
- Your new voice now appears under My Voices wherever a voice is selected.
Avatar Studio
Core/avatarstudio- What it does
- Your personal library of avatars — the AI "presenters" used in talking videos. Browse the avatars you've created, and generate new ones (from an uploaded photo, which is turned into a moving avatar, or from a video). Created avatars become selectable in the Video Studio flows.
- How to use
- Open Avatar Studio → view "My Avatars" → use the generate/create option to add a new one → it then appears as a choice when building Talking / Product / Fashion avatar videos.
- Endpoints
- POST
/get-user-avatar-list(your avatars),/add-user-avatar(create from image or video),/generate-avatar(product/fashion avatar). Template lists via/get-video-avatar-listand/get-product-avatar-list. - Provider
- Kie (generates the avatar clip), then NewPortAI (lip-sync when the avatar speaks)
- Models
UserAvatar(your avatars),VideoAvatar/ProductAvatar(the ready-made catalog)- Cost
- Creating an avatar from an image: 5 credits · from a video: 2 credits (charged on success)
How the user uses it
- Open Avatar Studio to see My Avatars.
- To make a new one, upload a photo (or video) and generate.
- Wait for it to finish processing.
- It then appears as a selectable presenter when you build Talking / Product / Fashion avatar videos.
Creator Studio
Core/creator-studio/:type- What it does
- A flexible, "power user" generator that isn't tied to one fixed flow. It reads the catalog of available AI models and builds its input form dynamically based on the model you pick — so the same page can drive different image or video models with their own settings (e.g. duration, aspect ratio).
- How to use
- Open Creator Studio (Video Models or Image Models) → pick a model → the form adapts to that model's options → generate. Output lands in Projects.
- Endpoints
- POST
/get-model-list(available models, by type) →/generate-studio-media(dispatches to studio image or video) - Provider
- Kie
- Models
AIModelCatalog(model definitions + pricing),ImageGeneration/UserVideos(output)- Cost
- Varies by model and settings. The server calculates it from the model's
pricingMatrix(per resolution and duration) orcostPerSecond— the price shown in the browser is never trusted. A model without a price cannot be used ("This model has no price configured").
How the user uses it
- Open Creator Studio and choose Video Models or Image Models.
- Pick a model — the form updates to show that model's options.
- Set the options (e.g. aspect ratio, duration) and your prompt.
- Click Generate; the result lands in Projects.
Feature Guide — AI Ad Tools #
Brand Kits
Core/tool/brand-kits- What it does
- Save a brand identity (logo, colours, tagline, voice, keywords) and apply it so generations stay on-brand. Text tools get a full brand context; image generation gets a concise colour/style directive.
- Endpoints
brand-kits/list · /create · /update · /set-default · /delete- Models
BrandKit- Cost
- Free (rides along with the generation it enhances)
- Applied in
- Image Studio, AI Writer (ad scripts), Sales Copy, Ad Copy Studio
How the user uses it
- Open Brand Kits → Add Brand Kit.
- Fill in your logo, colours, tagline, brand voice, and keywords.
- Tick Set as default and click Create.
- In Image Studio, Ad Copy Studio, or the AI Writer, pick this kit to keep results on-brand.
Ad Copy Studio
Core/tool/ad-copy-studio- What it does
- Generates multiple structured ad variants (headline + body + CTA) following a chosen framework (AIDA, PAS, FAB…), platform, tone, and language — with saved history.
- Endpoints
ad-copy/generate · /history · /delete- Provider
- Kie (LLM)
- Models
AdCopy- Cost
LLM_TEXT_CREDITSper successful generation (default 0.1; set in Admin → Settings → Credits)
How the user uses it
- Open Ad Copy Studio and enter your product name.
- Choose a framework, platform, tone, language, and number of variants.
- (Optional) select a Brand Kit, then click Generate Ad Copy.
- Copy any variant with one click; open History to revisit past generations.
Sales Copy
Core/tool/sales-copy- What it does
- The original single-block marketing-copy writer (Ad Copy Studio's predecessor). No sidebar link — reached by URL.
- Endpoint
generate-llm-text(type=marketingCopy)- Cost
LLM_TEXT_CREDITSper successful generation (default 0.1)
How the user uses it
- Go to the Sales Copy page (via its URL
/tool/sales-copy). - Enter the product name and industry (plus any optional fields).
- (Optional) pick a Brand Kit, then click Generate.
- Copy the finished marketing copy.
Ad Localizer
CoreCredits/tool/ad-localizer- What it does
- Recreates a completed talking-avatar video in other languages: translate the script → re-voice → re-lip-sync. One source video becomes many localized videos in Projects.
- Endpoints
ad-localizer/languages · /sources · /localize- Providers
- Kie (translate) + Speechify (voice) + NewPortAI (lip-sync)
- Models
UserVideos(withlanguage+localizedFrom)- Cost
- 2 credits per language plus the AI text cost (
LLM_TEXT_CREDITS) for each translation — checked up front for the whole batch
How the user uses it
- First, create a Talking Avatar Video and let it finish.
- Open Ad Localizer and select that video as the source.
- Paste the original script (the words spoken in the video).
- Select the target languages — the credit cost updates live.
- Click Localize, then watch Projects for each localized video.
Feature Guide — Payments (Stripe) #
One-time credit packs, purchased through Stripe Checkout. Optional — without Stripe keys the rest of the app works normally. Setup steps: Admin → Settings → Payments.
The flow
- Admin defines packs on
/admin/credit-packs(name, credits, price, currency). - User opens
/buy-credits, clicks Buy → backend creates a pendingPaymentOrder+ a Stripe Checkout session and redirects to Stripe. - User pays (test card
4242 4242 4242 4242). Stripe returns them to/payment-success. - Credits are granted by
finalizeSession, triggered by both the Stripe webhook and the success-page verify call — made idempotent by thecreditGrantedflag so credits are never granted twice. Before granting, the paid amount, currency and session are checked against the stored order.
Architecture notes
- The webhook POST
/api/payment/stripe/webhookis mounted specially inapp.jswith a raw body parser (before JSON parsing) so Stripe's signature can be verified. A valid signature (STRIPE_WEBHOOK_SECRET) is required; unsigned calls are refused. - Webhook events used:
checkout.session.completedandcheckout.session.async_payment_succeeded. - The Stripe client is lazily initialised — the app boots fine before keys are added; Buy Credits
shows "Payments are not configured yet" until
STRIPE_SECRET_KEYis saved in Admin → Settings (no restart needed). - Users can only see their own payment orders.
- Models:
CreditPack(what's for sale),PaymentOrder(audit trail + idempotency).
Feature Guide — Community & Projects #
Projects
Core/projectsThe user's library of generated images and videos. This is where async jobs are watched (Processing →
Completed/Failed) and downloaded/deleted (get-user-videos-list, get-generated-images-list, delete-data).
How the user uses it
- Open Projects and switch between the Videos and Images tabs.
- Check each item's status: Processing, Completed, or Failed.
- On a completed item, download it or delete it.
Community
Core/communityA social feed: posts (up to 3 images), likes, comments, top contributors, and trending hashtags. Admins can create categories and pin posts.
Endpoints: community/get-posts · create-post · update-post · delete-post · toggle-like · get-comments · add-comment · delete-comment · get-top-contributors · get-trending-hashtags.
How the user uses it
- Open Community.
- Click to create a post — add a title, text, and up to 3 images.
- Like and comment on other posts; browse trending hashtags and top contributors.
Admin Panel #
Accessible to admin role accounts under /admin/*. Every admin API route is protected on the server: not logged in → 401,
logged in but not an admin → 403.
| Page | Route | Purpose |
|---|---|---|
| Dashboard | /admin/dashboard |
Totals (users, active users, checkouts, paid orders) and a new-users chart for a chosen date range. |
| Users | /admin/users |
Admin accounts are not listed here, only customer accounts. Add users (name, email, password, optional starting credit, optional welcome email with a "set your password" link), edit a user's name and password, add credits, set active/inactive and delete users. A user's email and role cannot be changed. |
| Prompts | /admin/prompts |
Manage the preset-prompt library (prompt text, type and an example image). Starts empty — see Add your own content. |
| Product Avatar Templates | /admin/product-avatar-templates |
Upload and manage the ready-made product/fashion avatars (requires S3). |
| Video Avatar Templates | /admin/video-avatar-templates |
Upload and manage the ready-made talking avatars (requires S3). |
| User Analytics | /admin/user-analytics |
Per-user creation counts. |
| Community Categories | /admin/community-categories |
Create, edit, reorder and delete community categories (SVG icons are sanitised). |
| Community | /admin/community |
Moderate the community feed: pin posts and remove posts. |
| Credit Packs | /admin/credit-packs |
Create & price the packs users buy. |
| Settings | /admin/settings |
Provider keys, storage, payments, email, site URLs and credit prices — with Test buttons. See Admin → Settings. |
The admin tasks you'll use most
- Create a user — Admin sidebar → Users → Add User → name, email, password and an optional credit amount → Add User. Tick Send Email to send the welcome email with a link to set their own password.
- Give a user credits — Admin sidebar → Users → find the user → Edit → enter an amount under Assign Credit → Update. The amount is added to the user's balance.
- Set up something to sell — Admin sidebar → Credit Packs → Add Credit Pack → enter a name, credits, and price → Save. It then appears on the user's Buy Credits page.
- Change a provider key — Admin sidebar → Settings → edit the field → Save → Test.
Deactivating or deleting a user, or setting a new password for them, logs that user out immediately.
Roles cannot be changed: there is one admin account, created by npm run setup.
A deleted user cannot sign up again with the same email; to bring the account back, add it again in
Admin → Users → Add User — it is restored with its previous data and credits.
Data Models Reference #
26 Mongoose models. All use timestamps; most user-owned models share a platform sub-object and isDeleted/deletedAt soft-delete.
Users & Billing
| Model | Purpose | Key fields |
|---|---|---|
User |
Account | name, email (unique), password (bcrypt), role (user/admin), status, isSpecialUser, userSource, isDeleted, tokenVersion, resetPasswordToken (SHA-256 hash), resetPasswordExpires |
Credit |
Granted credit batch | userId, creditsAssigned, creditsUsed, type (creditPurchased/assignbyadmin), status, expireAt, orderId |
CreditUsed |
Deduction log | userId, creditsUsed, type (16 feature types: imageGeneration, talkingAvatar, productAvatar, fashionAvatar, fashionShow, productShoot, aiAds, imageToAvatar, videoToAvatar, voiceClone, textToSpeech, studioVideo, studioImage, llmText, ttsPreview, adLocalizer) |
CreditReservation |
In-flight hold | userId, taskId, creditsUsed, deductType, type (kie/newportrai), status (RESERVED/COMPLETED/RELEASED) |
CreditLock |
Per-user lock | Short-lived lock that serialises balance checks and deductions for one user |
CreditPack |
Purchasable pack | name, credits, price, currency, isPopular, sortOrder, status |
PaymentOrder |
Purchase attempt | userId, packId, credits, amount, gateway, gatewaySessionId, status, creditGranted, paidAt |
CreationCount |
Usage counters | per-feature counts + last-track dates, overallCount |
Videos, Avatars & Images
| Model | Purpose | Key fields |
|---|---|---|
UserVideos |
A video job/output | userId, title, avatarType/avatarId, videoUseCaseType, portrait{image,video}, status, renderData, rawData, language, localizedFrom |
UserAvatar |
User's custom avatar | userId, avatarType, portrait, voice, status, renderData, usedCount |
VideoAvatar |
Catalog talking avatar | fullName, gender, voice, portrait/landscape/squared media, status |
ProductAvatar |
Catalog product/fashion avatar | fullName, avatarType (Product/Fashion), voice, media, status |
ImageGeneration |
An image job/output | userId, prompt, useCaseType, images[], status, renderData |
PredictionReplicate |
Async task tracker | taskId (unique), taskModel, taskType, taskStatus, taskOutput |
UserMediaUploads |
Uploaded media | userId, title, mediaType (video/image/audio/voiceover), audio/image/video, mettaData |
Voice, Community & Content
| Model | Purpose | Key fields |
|---|---|---|
UserVoice |
Cloned voice | userId, id (provider), display_name, locale, gender, provider, status |
TtsSpeechify |
Voice catalog | id, display_name, gender, locale, accent, use_case[], voice_style[], models[] |
CommunityPost |
Feed post | userId, title, description, category, hastags[], images[], stats{likes,comments,shares}, isPinned |
CommunityPostComments |
Comment | postId, userId, comment |
CommunityPostLikes |
Like | postId, userId (unique together) |
CommunityCategories |
Category | name, description, icon, ranking |
PrePrompt |
Preset prompt | title, prompt, type, image, status |
SiteData |
Key/value config | key, value (Mixed). Keys include ethnicities, profession and
app_credentials (Admin → Settings, secrets encrypted) |
AIModelCatalog |
Model catalog | id, title, type (image/video), costPerSecond, pricingMatrix, settings |
BrandKit |
Brand identity | userId, name, logo, colors{}, tagline, industry, brandVoice, keywords[], isDefault |
AdCopy |
Ad-copy generation | userId, product/framework/tone/platform/language, brandKitId, variants[{headline,primaryText,cta}] |
API Reference #
All paths are prefixed /api. GET/POST = method; Auth = requires a valid token; Admin = requires a
logged-in admin.
Common responses
| Situation | HTTP status | Body |
|---|---|---|
| Missing, invalid, expired or revoked token | 401 |
{"status":"error","errorCode":"InvalidToken","message":"…"} |
Logged in but not an admin (any /admin/* route) |
403 |
{"status":"error","errorCode":"Forbidden","message":"…"} |
| Rate limit exceeded | 429 |
Error message (e.g. "Too many login attempts. Please try again in 15 minutes.") |
| Unexpected server error | 500 |
Generic message only — no stack traces or internal details. |
System
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /health |
Public | Health check — returns {"status":"success","message":"ok"} |
| GET | /config |
Public | Non-secret public settings for the frontend: SUPPORT_URL, APP_NAME and the
current credit prices object |
| POST | /get-site-data |
Auth | Public site lists only (ethnicities, profession); saved API keys can
never be read through the API |
Auth & Profile
| Method | Path | Auth | Purpose |
|---|---|---|---|
| POST | /auth/login |
Public | Log in, issue JWT (rate-limited) |
| POST | /auth/register |
Public | Sign up: body { name, email, password } creates a regular user account and returns a JWT
(20 per hour per IP) |
| POST | /auth/logout |
Auth | Log out and revoke all of the user's tokens |
| POST | /auth/forgot-password |
Public | Email a reset link (same response for unknown emails; 5/hour) |
| POST | /auth/reset-password |
Public | Body { token, password } — token is the opaque 64-hex value from the email
link; single use, valid 1 hour |
| GET | /auth/get-auth-user |
Token | Rehydrate session |
| POST | /update-profile |
Auth | Update name/picture; changing the password requires currentPassword |
Billing & Payments
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /get-billing-info |
Auth | Credit balances + history |
| GET | /payment/credit-packs |
Auth | Active packs |
| POST | /payment/create-checkout-session |
Auth | Start Stripe checkout |
| POST | /payment/verify-order |
Auth | Confirm/finalize after return |
| POST | /payment/history |
Auth | Purchase history |
| POST | /payment/stripe/webhook |
Public (signed) | Grant credits on payment |
| POST | /admin/payment/{get-packs,save-pack,delete-pack} |
Admin | Pack management |
Generation — Image, Video, Voice, LLM
| Method | Path | Purpose |
|---|---|---|
| POST | /generate-images |
Text-to-image |
| POST | /generate-product-shoot-images |
Product shoot images |
| POST | /combine-avatar-product |
Composite avatar + product |
| POST | /generate-avatar |
Product/fashion talking avatar |
| POST | /generate-video |
Fashion/product video |
| POST | /generate-ai-ads-video |
AI ad video |
| POST | /generate-user-avatar-video |
Talking-avatar lip-sync |
| POST | /generate-studio-media |
Studio image/video |
| POST | /synthesize-text |
Text-to-speech |
| POST | /add-user-voice |
Clone a voice |
| POST | /generate-ad-script |
LLM ad script |
| POST | /generate-llm-text |
LLM marketing copy |
| POST | /generate-video-prompt |
LLM video prompt from image |
New Feature Endpoints
| Method | Path | Purpose |
|---|---|---|
| GET/POST | /brand-kits/{list,create,update,set-default,delete} |
Brand Kits CRUD |
| POST | /ad-copy/{generate,history,delete} |
Ad Copy Studio |
| GET/POST | /ad-localizer/{languages,sources,localize} |
Ad Localizer |
Lists, Media, Community, Admin, Webhooks
| Method | Path | Purpose |
|---|---|---|
| POST | /get-generated-images-list · /get-user-videos-list |
List a user's media |
| POST | /get-{product-avatar,video-avatar,user-avatar}-list |
Avatar lists |
| POST | /get-premium-voices-list · GET
/get-user-voices-list
|
Voice lists |
| POST | /file-to-upload · /get-user-uploads · /delete-user-uploads · /get-thirdParty-media |
Media & uploads |
| POST | /get-model-list |
Active Creator Studio models, body { type: "image" | "video" } (Auth) |
| POST | /add-user-avatar |
Create an avatar from an uploaded image or video URL, body { avatarUploadType, fileUrl, title, … }.
An image starts a generation (CREDITS_AVATAR_FROM_IMAGE, charged on success); a video is
charged at once (CREDITS_AVATAR_FROM_VIDEO) (Auth) |
| POST | /add-manual-image |
Add an existing image URL ({ image, useCaseType }, http or https) to the user's images; no
generation and no credits (Auth) |
| POST | /delete-data |
Delete one of the caller's own items, body { type, id } where type is
userVoice, userAvatar, userVideo, generatedImages or
userImage; generatedImagesIndex with imageIndex removes one image
from a generation (Auth) |
| GET | /community/get-categories |
Community categories (public, no login needed) |
| POST | /community/* |
Feed, likes, comments (Auth) |
| POST | /admin/{get-users,add-user,update-user,delete-user,update-user-status} |
User management (Admin). add-user always creates a regular user (name, email, password,
optional credit and sendMail). update-user takes
userId plus name, password and/or credit (added to the
balance); a different role is rejected. |
| POST | /admin/get-*-analytics |
Analytics (Admin) |
| POST | /admin/{get-avatar-templates,add-update-avatar,update-avatar-status,delete-avatar} |
Avatar template management (Admin) |
| POST | /admin/add-pre-prompt · /admin/delete-pre-prompt · /get-pre-prompt-list |
Preset prompts (add and delete: Admin; list: Auth). Delete takes { id } and also removes
the reference image. |
| POST | /admin/community/{add-category,update-category,delete-category,reorder-categories,pin-post}
|
Community administration (Admin) |
| POST | /admin/settings/{get,save,test} |
Admin → Settings: masked values, save, connection tests (Admin) |
| POST | /kie/webhook?token=… |
Kie callback — requires the secret token; each task processed once |
Security & Hardening #
The protections built into ShowcaseAI, how to test them, and what you still need to do on your own server.
Access control
- Every
/admin/*API route requires a logged-in admin: 401 when not logged in, 403 when not an admin. This includes the avatar-template and settings routes. /get-site-datarequires a login and only returns the public lists (ethnicities,profession). Saved API keys can never be read through the API.- Ownership is checked on every user resource: users can only view or delete their own media, voices, avatars, ad copies, brand kits and payment orders. Cloned voices can only be used by their owner.
- An admin cannot deactivate or delete themselves, and the last admin cannot be removed.
Sessions and passwords
- JWT signed with HS256 (algorithm pinned on verification), valid for
SESSION_DAYS(default 7 days); the browser keeps the sign-in for at most 7 days. - Per-user token version: logout, password change, and admin deactivation/deletion revoke all sessions of that user immediately. Inactive or deleted users cannot log in or use old tokens.
- Password reset uses a single-use random token; only its SHA-256 hash is stored and it expires after 1 hour. Welcome emails contain a 48-hour "set your password" link instead of a plaintext password.
- Minimum password length 8. Changing a password requires the current password. Forgot-password returns the same response for unknown emails (no account enumeration).
Request protection
- Rate limiting: failed logins 10 per 15 minutes per IP (
LOGIN_RATE_LIMIT); forgot-password and reset-password 5 per hour (one shared limit); sign-up 20 per hour; voice previews 20 per minute; a global API limit (API_RATE_LIMIT, 1000 requests per 5 minutes per IP). - Helmet security headers, a CORS allow-list (
FRONTEND_URL), request body size limits, and generic error messages (no stack traces or internal errors are sent to clients). - NoSQL injection: MongoDB operators are stripped from all request bodies, ids are validated, and user search text is escaped before it is used in a regular expression.
Credits and payments
- Prices are always calculated on the server (Creator Studio from the model catalog; text-to-speech from the script length), never taken from the browser.
- Credits are reserved atomically (per-user lock) before a paid provider is called and released
automatically on failure. Stale reservations are released after
RESERVATION_TTL_MIN. Duplicate provider callbacks cannot charge twice. - Kie callbacks must carry a secret token (HMAC derived from
WEBHOOK_SECRET, or fromJWT_SECRET) and each task is processed only once. - The Stripe webhook requires a valid signature; the amount, currency and session are checked against the order before credits are granted.
Files and outgoing requests
- Uploads: only image, video and audio files are accepted. The real file type is detected from the file
content (magic bytes); HTML and SVG uploads are never stored. Storage keys are generated by the server
inside the user's folder. Size limit
MAX_UPLOAD_MB; temporary files are deleted. - SSRF protection: when the server downloads a URL, only public http(s) addresses are allowed. Private,
loopback, link-local and cloud-metadata IP addresses are blocked, also after redirects and DNS
resolution. Downloads are limited in size (
MAX_REMOTE_FILE_MB) and time. In production only https URLs are allowed unlessALLOW_HTTP_REMOTE_URLS=true. - API keys saved in Admin → Settings are encrypted at rest (AES-256-GCM) and never sent back to the browser.
Frontend
- The login token is kept only in the
auth_tokencookie (SameSite=Lax, Secure on https) — not in sessionStorage. - Automatic logout on any 401 response.
- Notification text is rendered as plain text (no HTML injection). Community category SVG icons are sanitised with DOMPurify (and on the server).
- External links open with
noopener; a route error boundary shows a friendly error page instead of a blank screen.
Automated security checks
With the API running and setup finished, run from backend/:
$ SMOKE_ADMIN_PASSWORD="your-admin-password" npm run smoke # macOS / Linux, uses http://localhost:<PORT> $ API_URL=http://localhost:5000 SMOKE_ADMIN_PASSWORD="…" npm run smoke # explicit URL > $env:SMOKE_ADMIN_PASSWORD="your-admin-password"; npm run smoke # Windows PowerShell
npm run smoke runs scripts/security-smoke.js, which
you can also start directly with node scripts/security-smoke.js. The script reads backend/.env, logs in as the admin (SMOKE_ADMIN_EMAIL,
default admin@showcaseai.app, and SMOKE_ADMIN_PASSWORD — there is no default
password, so you must set it to your admin password), creates a temporary test user and runs 33 checks —
admin routes refused without login (401) and for normal users (403), saved keys never returned, NoSQL
injection rejected, reset links single-use, tokens revoked after logout, users unable to delete other
users' videos, client-supplied prices ignored, unsigned Kie/Stripe webhooks refused, HTML disguised as an
image rejected, and login rate limiting. It prints a ✔/✖ line per check and a "passed / failed" summary.
The temporary user and everything it created are deleted at the end; your real accounts are not
touched.
The smoke test deliberately triggers the login rate limit. Afterwards, wait 15 minutes or restart the API before logging in from the same computer.
Your hardening checklist
NODE_ENV=productionon the live server.- Strong, unique
JWT_SECRETandSETTINGS_ENCRYPTION_KEY(generated bynpm run setup). - HTTPS everywhere (frontend, API, webhooks).
- Restrict CORS to your real frontend origin.
- Use your own admin email and password (Changing the admin login).
- MongoDB with authentication enabled and network access restricted (Atlas IP allow-list or firewall).
- S3 IAM user with least privilege (see S3 setup).
- Keep dependencies updated (
npm audit) and back up MongoDB regularly.
Troubleshooting #
Start with npm run check inside backend/ — it finds most configuration problems in seconds. Then look up the
symptom below. The API terminal (or pm2 logs) always shows the underlying error.
| Symptom | Cause | Fix |
|---|---|---|
| Site won't load | One app isn't running | Make sure both the API and the frontend are running (two terminals, or PM2 + web server). |
| API does not start: "JWT_SECRET is not set" or "must be at least 32 characters" | Missing or weak secret in backend/.env |
On a new install run npm run setup (generates it). If you prepare .env by
hand, put a random value of at least 32 characters in it (see Secrets) and never
change an existing one. On an installed site, restore the value you used before from your backup of
.env; only if it is lost, put in a new value (everyone is logged out). |
| "Could not connect to MongoDB" | Wrong mongoDBURL, MongoDB not running, or Atlas blocking your IP |
Check the connection string (including user, password and database name); start mongod
for a local install; on Atlas add the server's IP under Network Access. |
| Login says "Too many login attempts" | 10 failed logins from your IP in 15 minutes (also caused by the security smoke test) | Wait 15 minutes, or restart the API (limits are kept in memory). |
| Sign-up or Forgot password says "Too many requests" | More than 5 sign-up, forgot-password or reset-password requests from your IP in one hour | Wait an hour, or restart the API (limits are kept in memory). |
| Everyone is logged out / 401 after a deploy | JWT_SECRET changed |
Expected — log in again. |
| Saved keys "disappeared" or show as not configured after a restart or redeploy | SETTINGS_ENCRYPTION_KEY (or JWT_SECRET, when no encryption key is set) is not the value the keys were saved with: .env was replaced, regenerated or restored from an old copy. The API log says "could not decrypt … was SETTINGS_ENCRYPTION_KEY / JWT_SECRET changed?" |
Admin → Settings shows a warning, and npm run check reports the keys that cannot be read. Put the original values back into backend/.env (from your backup) and restart; the keys come back. Only if the original is truly lost, re-enter every key in Admin → Settings. |
Secrets – set once, never change: .env lost or secrets regenerated while the database has data |
A newly generated key cannot decrypt data encrypted with the old one; changing JWT_SECRET only logs users out. |
Restore backend/.env from your backup. Run npm run setup before using the app or saving any key, then back up .env. --rotate-secrets replaces only JWT_SECRET. See Secrets – set once, never change. |
npm run setup says "already set up" |
The database already has an admin; setup runs once and never overwrites your data | Nothing to do — log in. To start over use npm run setup -- --reset (deletes all data). |
| Locked out of the admin panel | Forgotten password | Use Forgot password, or set a new password with MongoDB Compass (steps). |
| Browser console shows a CORS error | The frontend origin is not allowed | Set FRONTEND_URL (Admin → Settings → Site & Branding) to the exact origin — scheme, host and
port, e.g. https://app.example.com or http://localhost:4001. Restart the
API. |
| Frontend calls the wrong API / old value | VITE_* values are compiled in |
Fix frontend/.env and run npm run build again (or restart
npm run dev). |
| "Insufficient credit" | Empty balance | Assign credits in Admin → Users, or buy a credit pack. |
| Generation stuck on "Processing" | Kie cannot reach BACKEND_URL |
BACKEND_URL must be a public https URL reachable by Kie (use ngrok locally). Reserved
credits return automatically after RESERVATION_TTL_MIN (default 120 minutes). |
| "File storage is not configured" / uploads fail | S3 settings missing or wrong | Admin → Settings → Storage → Test. Check the bucket policy allows public read and the IAM user can write. |
| Generation fails right after upload | Provider cannot download the file | Make sure S3 files are publicly readable (open a file URL in a private browser window). |
| The "Try Samples" images on the Create pages do not load | The host set in VITE_SAMPLE_MEDIA_BASE is unreachable (offline, blocked, or your own host is
missing the files) |
Check VITE_SAMPLE_MEDIA_BASE in frontend/.env and the file layout described
in Sample images, then rebuild the frontend. |
| "The AI service is not configured yet" | No Kie key | Enter KIE_API_KEY in Admin → Settings → AI Providers and click Test. |
| Emails not arriving | No email provider, or wrong SMTP details | Configure Admin → Settings → Email. Look for "Email not sent" in the API log. Port 465 = SSL, 587 = STARTTLS. |
| "This model has no price configured" | The Creator Studio model has no price | Set pricingMatrix or costPerSecond on that document in the
aimodelcatalogs collection. |
| "Payments are not configured yet" | No Stripe secret key | Enter STRIPE_SECRET_KEY in Admin → Settings → Payments. |
| Stripe payment succeeded but no credits | Webhook missing or misconfigured | Check the webhook URL (<BACKEND_URL>/api/payment/stripe/webhook), the two events,
and STRIPE_WEBHOOK_SECRET. Stripe's dashboard shows each delivery attempt. |
| Ad Localizer language "Failed" | Translate/voice/lip-sync step errored | Hover for the reason; verify all three provider keys with their Test buttons. |
| Refreshing a page returns 404 in production | No single-page-app fallback | Configure the web server to send unknown paths to index.html (nginx: try_files
$uri /index.html;). |
| Large uploads fail with 413 in production | nginx body size limit | Set client_max_body_size 100m; (as in deploy/nginx.conf.example). |
Glossary #
| Term | Meaning |
|---|---|
| Credit | The app's usage currency. Every generation spends credits. |
| Reservation | A temporary hold on credits while an async job runs; becomes a real charge on success or is released on failure. |
| Lip-sync | Matching an avatar's mouth movements to an audio track (NewPortAI). |
| Avatar | The AI "presenter" in a talking video — from the catalog or user-created. |
| Brand Kit | A saved brand identity applied to generations to keep output on-brand. |
| Webhook | A callback an external service (Kie, Stripe) makes to the backend to report an event. |
| JWT | JSON Web Token — the signed login token carried on every request. |
| IPN | Instant Payment/Process Notification — the name used for the Kie completion webhook. |
| Starter data | Data every installation needs: model catalog with example prices, avatar-form lists, community categories and credit packs. |
| Demo data | The demo user (user@showcaseai.app) and that user's starting credits. For local testing only; never created when NODE_ENV=production. |
| Seed | The starter data (admin, demo user, model catalog, community categories, credit packs, dropdown lists) that npm run setup loads into MongoDB. Prompts and avatar templates are added by you. |
| Admin → Settings | The admin screen where provider keys and site options are stored (encrypted) in the database. |
| Tunnel | A tool such as ngrok that gives a local API a temporary public https URL so providers can call it. |