Developer API

Add AI dialogue to your Ren'Py game.
No GPU. No Ollama. 3 endpoints.

We run the LLM. You send a prompt, you get text back. Free tier gives you 200 requests/day — enough to prototype and ship. Drop-in Python, works with any Ren'Py project.

The problem

You want NPCs that actually respond to the player instead of cycling through canned lines. You've looked at Ollama, llama.cpp, KoboldCpp. They work — until your players need a 3090 to run your visual novel.

This API lets you offload the LLM to us. Your game sends a prompt, gets a response. The player just needs a browser to log in once. After that, it's HTTP calls from your Ren'Py script.

"I'll just use my own OpenAI key"

You can. And if you have the time to build your own account system, token management, rate limiting, and billing dashboard — go for it. Seriously. But if you'd rather spend that time on your actual game, read on.

The core problem with shipping your own API key inside a Ren'Py game: it's a zip of Python files. Anyone can open it and extract your key in 30 seconds. Even if you obfuscate it — your game gets 500 downloads, that's 500 people burning through your credits. A popular game with active players can rack up hundreds of dollars in API costs in a week. That bill goes to you.

With this API, the player authenticates with their own free account. You ship zero API keys. You pay nothing. Each player gets their own rate limit. Your game scales to 10,000 players and your cost is still $0.

Your own key This API
API key in game files Yes — extractable None
Who pays per request You, the dev Nobody (free tier)
1,000 players × 100 req/day ~$15–50/day on your card $0 to you
Key gets leaked Your bill explodes N/A — no key to leak
Rate limiting Build it yourself Built in, per player
Auth & accounts Build it yourself Google login, handled

Could you build all of this yourself? Absolutely. But that's weeks of backend work that has nothing to do with making your game better. This exists so you can skip that part and focus on what matters — your characters, your story, your game design.

On the roadmap: We're building an affiliation program. Once our store page is live, devs whose games bring players to the platform will get a cut of the revenue. It's not ready yet — we're being upfront about that. Right now, this is just a free tool to help you ship your game without dealing with infra. The revenue sharing is coming.

How it works

Three steps. No SDK, no library to install. Just requests (comes with Ren'Py) and a few lines of Python.

  • Auth: Open the player's browser to our login page. We redirect back to localhost with a JWT. Done once.
  • Generate: POST your prompt + temperature + max_tokens. Get text back as JSON.
  • Health: Ping to check if the token is still valid. Optional but useful on game startup.

Endpoints

GET /game-auth?callback=http://localhost:{port}

Opens in browser. Player logs in with Google. Redirects back to your localhost callback with ?token=JWT&email=user@email.com.

POST /api/game/generate

Send a prompt, get generated text. Auth via Authorization: Bearer {JWT}. Returns {"response": "..."}.

GET /api/game/health

Returns {"status": "ok"} if the JWT is valid. 401 if expired. Use it on game launch.

Integration code

Copy-paste into your Ren'Py project. Adjust the prompt building to your game's characters.

1. Auth — get a token

Start a tiny HTTP server on localhost, open the browser, wait for the redirect with the token.

game/kraken_api.rpy
init python:
    import requests
    import webbrowser
    import threading
    from http.server import HTTPServer, BaseHTTPRequestHandler
    from urllib.parse import urlparse, parse_qs

    KRAKEN_URL = "https://smut.fr"
    AUTH_PORT = 52431

    # persistent token — survives game restarts
    if persistent.kraken_token is None:
        persistent.kraken_token = None
    if persistent.kraken_email is None:
        persistent.kraken_email = None

    class _AuthHandler(BaseHTTPRequestHandler):
        """Catches the redirect from the website."""
        token = None
        email = None
        error = None

        def do_GET(self):
            params = parse_qs(urlparse(self.path).query)
            if "token" in params:
                _AuthHandler.token = params["token"][0]
                _AuthHandler.email = params.get("email", [""])[0]
                body = b"<h2>Logged in! You can close this tab.</h2>"
            else:
                _AuthHandler.error = params.get("error", ["Unknown error"])[0]
                body = b"<h2>Login failed. Check the game.</h2>"
            self.send_response(200)
            self.send_header("Content-Type", "text/html")
            self.end_headers()
            self.wfile.write(body)

        def log_message(self, *args):
            pass  # silence console noise

    def kraken_login():
        """Opens browser, waits for token, stores it."""
        _AuthHandler.token = None
        _AuthHandler.error = None
        srv = HTTPServer(("127.0.0.1", AUTH_PORT), _AuthHandler)
        srv.timeout = 120  # 2 min max wait

        webbrowser.open(
            "{}/game-auth?callback=http://localhost:{}".format(KRAKEN_URL, AUTH_PORT)
        )
        srv.handle_request()  # blocks until redirect arrives
        srv.server_close()

        if _AuthHandler.token:
            persistent.kraken_token = _AuthHandler.token
            persistent.kraken_email = _AuthHandler.email
            return True
        return False

2. Generate — get AI dialogue

game/kraken_api.rpy (continued)
    def kraken_generate(prompt, temperature=0.7, max_tokens=80):
        """Send a prompt, get text back. Returns None on error."""
        if not persistent.kraken_token:
            return None
        try:
            r = requests.post(
                "{}/api/game/generate".format(KRAKEN_URL),
                json={
                    "prompt": prompt,
                    "temperature": temperature,
                    "max_tokens": max_tokens,
                },
                headers={
                    "Authorization": "Bearer " + persistent.kraken_token,
                    "Content-Type": "application/json",
                },
                timeout=15,
            )
            if r.status_code == 200:
                return r.json().get("response", "")
            if r.status_code == 401:
                persistent.kraken_token = None  # token expired
            return None
        except Exception:
            return None

    def kraken_health():
        """Returns True if the token is still valid."""
        if not persistent.kraken_token:
            return False
        try:
            r = requests.get(
                "{}/api/game/health".format(KRAKEN_URL),
                headers={"Authorization": "Bearer " + persistent.kraken_token},
                timeout=5,
            )
            return r.status_code == 200
        except Exception:
            return False

3. Use it in your script

game/script.rpy
label tavern:
    scene bg tavern
    show eileen happy

    # build your prompt however you want
    $ prompt = """You are Eileen, a sharp-tongued tavern owner.
You're tired, it's late, and the player just walked in.
Respond in 1-2 sentences. Stay in character.

Player: Got any rooms?

Eileen:"""

    $ reply = kraken_generate(prompt, temperature=0.8, max_tokens=60)

    if reply:
        e "[reply]"
    else:
        # fallback — hardcoded line if API is down
        e "We're full. Try the inn down the road."
The prompt is yours. We don't build it for you. You control the character definition, context window, and format. The API just takes whatever string you send and returns generated text. Build your prompts however your game needs them.

Request & response format

POST /api/game/generate

Request
{
    "prompt": "You are Eileen, a tavern owner.\n\nPlayer: Got any rooms?\n\nEileen:",
    "temperature": 0.7,
    "max_tokens": 80
}
Response (200)
{
    "response": "We're full tonight. But if you can handle a cot by the fireplace, that'll be two silver."
}
Errors
// 401 — token expired or invalid
{"error": "Invalid or expired token"}

// 429 — hit the daily limit
{"error": "Rate limit exceeded. Try again later."}

// 400 — no prompt provided
{"error": "Missing required field: prompt"}

temperature (0.0–1.0, default 0.7) and max_tokens (1–500, default 80) are optional. Prompt is required.

Rate limits

Per-user, per-day. The limits apply to the player's account, not your game — so if 100 players use your game, each gets their own quota.

Tier Requests / day Notes
Free 200 Enough for a few hours of gameplay
Premium 2,000 For heavier usage or longer sessions

When a player hits the limit, the API returns 429. Handle it with a fallback line in your script — the player won't notice.

What devs are building with this

  • Dynamic NPC dialogue — characters that react to what the player actually says instead of branching through a dialogue tree
  • Adaptive narration — story text that shifts based on player choices without writing every branch by hand
  • NPC memory — stuff conversation history into the prompt so the bartender remembers you from last time
  • Procedural quests — let the LLM generate quest descriptions, rumors, and item lore on the fly
  • Player interrogation / investigation — let players ask NPCs open-ended questions in detective or mystery VNs

The pattern is always the same: build a prompt, call the API, use the response as dialogue text. How you build the prompt is where your game design lives.

Common questions

Which LLM are you running?

We route through our own API keys. Currently using fast models optimized for short-form dialogue. You don't need to know or care — you send text, you get text. If we switch providers, your game doesn't break.

Does the player need an account on your site?

Yes. They sign up with Google (one click) the first time. After that, the JWT lasts 30 days. They won't see the login again for a month.

Can I use this for NSFW content?

The prompt is yours. We don't filter input or output. Use responsibly.

What if your API goes down?

Always have a fallback. A hardcoded line, a random pick from a list — anything. Your game should never soft-lock because an HTTP call failed. Wrap the call in a try/except like the code above does.

Can I use this outside of Ren'Py?

Yes. It's a standard REST API. Unity (C# HttpClient), Godot (HTTPRequest), RPG Maker (via plugins), raw Python, whatever. The endpoints don't care what's calling them.

Is there a "Powered by" requirement?

Not required, but appreciated. A small credit in your game's settings or about screen helps other devs discover the API. Once our affiliation program launches, a visible mention will be how we track attribution for revenue sharing — so it'll be in your interest to include it.

What's this about revenue sharing?

We're planning an affiliation program: games that bring players to the platform will earn a percentage of any premium subscriptions those players buy. Our store page isn't live yet, so this isn't active — but it's on the roadmap. We're telling you now so you know the direction.

Ready to try it?

Copy the integration code above, drop it in your Ren'Py project, and you're done. No API key to manage, no account to create on your side. Your players handle their own login — you just write the game.