-
Step 1 · Pick a model and endpoint; make one call work first
Use an OpenAI-compatible API; get curl working before you write any page
Don't get lost in the options: choose a provider that speaks the OpenAI-compatible protocol. Then switching models later is a
base_urlchange and your code stays identical.Make one call from your terminal first, to confirm the key, the model name and the billing all work:
curl https://<provider>/v1/chat/completions \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{"model":"<model>","messages":[{"role":"user","content":"say something"}]}'Move on only once you can read
choices[0].message.content.Three things to weigh when picking a model: quality in your target language, price per million tokens, and whether there's a free allowance. For a personal site, starting with a provider that hands out credit is the cheapest way to learn whether anyone actually uses it.
Checklist
- curl returns a normal reply
- You know the model name and its unit price
- Confirmed the endpoint is OpenAI-compatible
-
Step 2 · The key never reaches the browser
Calling from the front end publishes your key — put a proxy in front
This is the most expensive beginner mistake: putting the API key in front-end JavaScript is handing out money. Anyone can press F12, copy it, and drain your credit within hours.
Three correct approaches, cheapest first:
- A cloud function as proxy (recommended): one function takes the user's question, calls the model with the key attached, and returns the result. Free tiers are enough for a personal site
- A ready-made relay: open-source projects that ship with key distribution, quota control and channel management — install and go
- An Nginx reverse proxy: proxy
/v1/to the provider and inject the key in a header. Simple to configure, weak on quota control
Whichever you pick, do these three:
- Cap max tokens per request and the request rate
- Rate-limit per IP or per session
- Log usage, so an anomaly is visible immediately
Checklist
- No key string can be found anywhere in the front-end code
- The real key isn't visible in the browser's network tab
- Rate limiting and a per-request token cap are in place
-
Step 3 · Build the chat page: streaming + context
Read the stream with fetch, render token by token, keep only recent turns
The experience that matters on a chat page is streaming — output starts appearing within a second of sending, instead of a ten-second blank wait.
const res = await fetch('/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model, stream: true, messages }) }); // read res.body with TextDecoder and parse the SSE 'data:' lines chunk by chunkContext control — this is where the money goes:
- Keep only the last 5–8 turns; drop anything older
- Fix one system prompt stating the assistant's identity, tone and what it must not promise
- Summarise long conversations, or start a new session
Fallbacks: a retry button on network failure; "the model is busy, try again shortly" on timeout; "copy" and "regenerate" buttons when an answer finishes. Never leave the page spinning.
Checklist
- Answers stream token by token instead of appearing all at once
- Multi-turn conversation keeps context, and trims past 8 turns
- Losing the network shows a clear message and a retry
-
Step 4 · Add guardrails: prompt, sensitive content, cost ceiling
Scope the answers, filter the input, set a daily budget
A public chat page needs guardrails, or you'll meet "credit drained by a bot" and "talked into saying something it shouldn't" on the same day.
Three layers:
- Prompt constraints: the system prompt states "answer only questions about X; decline politely outside that", and "never provide other people's private information or illegal content"
- Input filtering: run a local sensitive-word list first and refuse on a hit — saves money as well as trouble
- A cost ceiling: a daily budget; once it's exceeded, return "today's quota is used up, come back tomorrow"
State it on the page: replies are generated by AI and are for reference only; you don't collect personal information; conversations may be logged to improve the service — if you do log them.
Check usage once a day: if the usage curve suddenly goes vertical, you're probably being farmed. Lower the limits immediately.
Checklist
- Off-topic questions are politely declined
- A daily budget is set and you've verified it triggers
- The page carries an AI disclaimer
-
Step 5 · Launch + conversion tracking + monetisation
Instrument real usage; put provider affiliate links at the bottom
Once the chat page is live you'll want to know: is anyone using it, how many turns before they leave, and does anyone click the promo.
Events to instrument (definitions in
data/conversion.json, reported throughTracker.track):chat_start: first message sentchat_turn: each completed exchange, with the turn index in propschat_error: API failureaff_click: model or server promo clicked
Three numbers to watch: turns per person (under 2 means the first answer is poor), the
chat_errorshare (above 5% means investigate the API), andaff_click / chat_start— the real conversion rate.Monetisation: someone on an AI chat page already wants to use a model, so an invite link to a model provider at the bottom converts naturally. Add a server link too — "want to build your own?". Label promo slots "sponsored · affiliate".
Indexing: a chat page is a tool page, so its static part needs real copy explaining what it is and how to use it. Don't leave crawlers staring at an empty input box.
Checklist
- chat_start / chat_turn / aff_click are all queryable
- The static part of the page has crawlable explanatory copy
- Promo slots are labelled and the footer carries a disclaimer