AI
Connect one AI provider, prove it answers through the app's own code, and set up callbacks for video.
The site can generate text, images or video through your provider when this check passes with the app running:
pnpm tsx scripts/check-ai.ts --url http://localhost:3000
Its last line must be AI: all checks passed. If your product makes images, the agent also runs it once with --image, which generates one small image and costs a few cents.
Supported providers
The list lives in config/ai-providers.ts. A provider is on when its key is set. Every key stays on the server.
| Provider | Env variable(s) | Can do |
|---|---|---|
| OpenRouter | OPENROUTER_API_KEY |
chat (one key, many vendors' models) |
| OpenAI | OPENAI_API_KEY |
chat, image |
| Anthropic | ANTHROPIC_API_KEY |
chat |
GOOGLE_GENERATIVE_AI_API_KEY |
chat, image | |
| DeepSeek | DEEPSEEK_API_KEY |
chat |
| xAI | XAI_API_KEY |
chat, image |
| fal.ai | FAL_KEY |
image, video |
| Replicate | REPLICATE_API_TOKEN |
image, video |
| KIE | KIE_API_KEY |
image, video (image references also need Storage) |
| Any OpenAI-compatible API | CUSTOM_OPENAI_API_KEY, CUSTOM_OPENAI_BASE_URL, NEXT_PUBLIC_CUSTOM_OPENAI_MODELS (id:Name,id2:Name2) |
chat |
fal reads one variable, FAL_KEY. There is no separate fal secret.
Start with one provider.
- Text only: OpenRouter.
- Images or video: fal or Replicate. Both do image and video.
- Add a second provider only when a model you want is not on the first.
You
| Value | Where you get it |
|---|---|
| Which provider | tell the agent what your product generates; it suggests one from the list above |
OPENROUTER_API_KEY |
openrouter.ai → sign in → Keys → Create key |
REPLICATE_API_TOKEN |
replicate.com/account/api-tokens |
FAL_KEY |
fal.ai → sign in → Dashboard → Keys → Add key |
DEEPSEEK_API_KEY |
platform.deepseek.com/api_keys |
OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, XAI_API_KEY, KIE_API_KEY |
the provider's console → API keys |
| Billing on the provider account | the provider's billing page. The API cannot add a card, so only you can |
| A tunnel login (video, local only) | only if the tunnel tool the agent starts asks you to log in |
These click paths could not be checked against anything in the repository (check the dashboard, menus move). If a page has moved, look for "API keys" in the provider's dashboard.
Agent
- Writes your key into
.env.localand runs the check. For each chat provider, it makes one tiny chat call through the same code the app uses. - Adjusts the model list in
config/ai-models.tsto the models you want to offer. The demos use the first entry of each list. - For images: generates one small image with the cheapest-looking model, or one you name.
- For video: sets
WEBHOOK_BASE_URL, the public address the provider calls back when a video is ready. Locally that is a tunnel tolocalhost:3000, because a provider cannot reach your machine directly. Video jobs are also kept in Redis, so it needsUPSTASH_REDIS_REST_URLandUPSTASH_REDIS_REST_TOKEN. - Turns on callback signature checks: Replicate's secret is read from your account and written for you; for fal it sets
FAL_VERIFY_WEBHOOKS=true(nothing to paste). - Confirms that every route that calls a paid provider either charges credits (and refunds them on failure) or is admin-only.
- Calls the image demo signed out and expects the sign-in wall, which proves the route is live.
- Asks you to try the demos as admin:
/ai-demo/chat(pick a model whose provider has a key), and/ai-demo/imageor/ai-demo/videoif you set those up.
The demo pages are admin-only and charge no credits; they exist to test your keys. A paid feature for your users is built with the add-tool-page skill (see Add a tool page).
What a pass looks like
✓ model registry: <n> chat, <n> image, <n> video models, every provider exists in config/ai-providers.ts
✓ provider keys set: OpenRouter
✓ openrouter: <model id> answered "ok" (<n> in / <n> out tokens)
<route>: admin-only, charges no credits (demo)
✓ http://localhost:3000/api/ai-demo/image → 401 "Please sign in to use the AI demo." (signed-out wall works, the route is wired)
AI: all checks passed
With --image, one more line reports the image: ✓ <provider>: <model> returned image/…, <size> KB … in <seconds>s. With a video provider, two more: ✓ <provider>: <url> reaches the app and ✓ video task store: Redis configured.
When it fails
| The check or the app says | What it means | Fix |
|---|---|---|
no AI provider key is set |
nothing pasted yet | pick a provider and paste its key |
<provider>: <model> failed: … not found / does not exist |
the provider retired or renamed that model | the agent updates config/ai-models.ts |
openrouter: … failed: Missing Authentication header |
an invalid OpenRouter key | paste a fresh key |
… 401 / 403 / invalid api key |
wrong key, or no billing on the account | new key; add billing |
… 429 / quota / insufficient |
out of provider credit, or rate-limited | top up; try again later |
replicate: REPLICATE_API_TOKEN rejected |
wrong token | copy it again from the api-tokens page |
WEBHOOK_BASE_URL is not set or is local |
video has no address to call back | the agent sets it (a tunnel locally, your domain in production) |
<url> answered 404 / 502 / 530 or unreachable |
the tunnel is down or points elsewhere, or the app is not running | the agent restarts the tunnel and the app and updates the address |
video task store has no Redis |
Upstash not set | set UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN |
… calls a paid provider for any signed-in user without consumeCredits |
a route generates for free | the agent adds the credit charge, or makes it admin-only again |
--url → 307 / 308 |
the address redirects (http to https, bare domain to www) |
use the final address |
in the app: Missing API key: X is not configured for Y |
the running app does not see the key | restart the app, or redeploy with the key in the production env |
Going live
- The same keys go into the production env, and
WEBHOOK_BASE_URLbecomes your real domain. NEXT_PUBLIC_CUSTOM_OPENAI_MODELSis read at build time: change it, then rebuild.- The agent reruns the check against your domain with the production env.
- You: set spending limits and billing alerts in each provider's dashboard. The API cannot set them.