What you need
Python 3 with requests, and a free API key from at least one provider: the Groq API (open models on very fast hardware), the Google Gemini API (Google's models; prompts on the free tier may be used to improve Google's products) or OpenRouter (one key for many models; its free models are limited to 50 requests a day).
Keep the key on your computer or server. Never put it in JavaScript on a web page: anyone could read it and use up your limits.
One request format for all three
Each provider takes a POST to /chat/completions under its own base address, with your key as a Bearer token and a list of messages. Every message has a role (system, user or assistant) and content. The answer is in choices[0].message.content. Only the base address, the key and the model name differ.
curl
curl -X POST 'https://api.groq.com/openai/v1/chat/completions' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"model": "openai/gpt-oss-20b", "messages": [{"role": "user", "content": "Say hello"}]}'
The chatbot
Save it as chatbot.py, set your key and run it, for example GROQ_API_KEY=... python3 chatbot.py, or PROVIDER=gemini GEMINI_API_KEY=... python3 chatbot.py. It sends the whole conversation each time, which is how these APIs remember context. An empty line ends it. When a provider refuses a request, for example over a rate limit, the script shows the reason and you can ask again.
Python
import os
import requests
# Three free tiers that use the OpenAI chat format: base address, the
# environment variable with your key, and the model
PROVIDERS = {
"groq": ("https://api.groq.com/openai/v1", "GROQ_API_KEY", "openai/gpt-oss-20b"),
"gemini": ("https://generativelanguage.googleapis.com/v1beta/openai", "GEMINI_API_KEY", "gemini-3.8-flash"),
"openrouter": ("https://openrouter.ai/api/v1", "OPENROUTER_API_KEY", os.environ.get("OPENROUTER_MODEL", "")),
}
def ask(messages, provider):
base_url, key_name, model = PROVIDERS[provider]
response = requests.post(
base_url + "/chat/completions",
headers={"Authorization": "Bearer " + os.environ[key_name]},
json={"model": model, "messages": messages},
timeout=60,
)
response.raise_for_status()
return response.json()["choices"][0]["message"]["content"]
if __name__ == "__main__":
provider = os.environ.get("PROVIDER", "groq")
history = [{"role": "system", "content": "You are a helpful assistant. Answer in a few sentences."}]
while True:
question = input("You: ").strip()
if not question:
break
history.append({"role": "user", "content": question})
try:
answer = ask(history, provider)
except requests.HTTPError as error:
print("The API said:", error.response.status_code, error.response.text[:200])
history.pop()
continue
history.append({"role": "assistant", "content": answer})
print("Bot:", answer)
Choosing a model on OpenRouter
OpenRouter's free models change often; their names end in :free. Its model list is public, so this prints the current ones. Set one as OPENROUTER_MODEL.
Shell
curl -s https://openrouter.ai/api/v1/models | python3 -c "import json,sys; print('\n'.join(m['id'] for m in json.load(sys.stdin)['data'] if m['id'].endswith(':free')))"
Going further
For a chat box on your website, put ask() behind a small endpoint on your own server and let the page call that endpoint, so the key never reaches the browser. Limit how often one visitor may ask, or one person can use up your free tier for everyone.
Long conversations cost more tokens with every message; keep the last ten or so. Model names change over time: when a provider retires one, pick a current one from its model list.
Other free tiers with the same format: GitHub Models, for prototypes with a GitHub account, and Cloudflare Workers AI.