Orishare
Recipes

Godot

One prompt: XP, levels, coins, streaks, badges, quests, leaderboards, leagues and referrals in a Godot 4 game, with HTTPRequest and a customer session.

A Godot game talks to Orishare as the signed-in player: the publishable key plus a customer session, exactly like Unity. The session comes from your game server (minted with the secret key) or from Firebase or Supabase sign-in in the game. Never put a secret key (osk_) or the agent token in the project: an exported game is public.

The prompt

Paste this into your coding agent from the root of the Godot project.

Add a reward system to this Godot 4 game with Orishare: XP and levels, coins, a daily streak
with repair, badges, a starter quest, a weekly XP leaderboard, a weekly league and friend
referrals. Follow https://docs.orishare.com/docs/recipes/godot.md and the pages it links, step by
step: sign up for Orishare yourself (no human needed), apply the game starter configuration in
the test environment, add the GDScript autoload (and the small session endpoint if the game has a server),
test every HTTP call the game makes against the test environment, then give me the claim link and a short
list of what you built. Never put a secret key (osk_) or agent token (opat_) in the project.

Steps

  1. Sign up with Start here for AI agents, then apply the game starter in the test environment: Game setup.

  2. Get a session for the player. A game needs one of: a game server of its own (any small HTTP service; a serverless function is enough), or Firebase or Supabase sign-in. With neither, add the small server: it is the only place the secret key can live. Get the session from your server (Web game, step 5) or by exchanging a Firebase or Supabase token (Game setup, identity).

  3. The autoload (res://orishare.gd, Project Settings, Autoload, name Orishare):

    extends Node
    
    @export var api := "https://api.orishare.com"  # your test or production origin
    @export var publishable_key := "opk_..."  # public, safe in the export
    @export var connection_id := ""            # ic_... with Firebase or Supabase
    var token := ""                            # memory only, never a file
    
    # Returns { "status": <HTTP code>, "body": <parsed JSON or null> }, so callers can tell, for
    # example, 409 insufficient_balance on a streak repair from other failures.
    func _call(method: int, path: String, body = null, as_customer := true, key := "") -> Dictionary:
        var http := HTTPRequest.new()
        add_child(http)
        var headers := PackedStringArray([
            "Authorization: Bearer " + publishable_key,
            "Content-Type: application/json",
        ])
        if method != HTTPClient.METHOD_GET:
            headers.append("Idempotency-Key: " + (key if key != "" else _uuid()))
        if as_customer:
            headers.append("X-Orishare-Customer-Token: " + token)
        var data := "" if body == null else JSON.stringify(body)
        http.request(api + path, headers, method, data)
        var result: Array = await http.request_completed  # [result, code, headers, body]
        http.queue_free()
        if result[1] == 401:
            token = ""  # expired: get a new session
        var text: String = result[3].get_string_from_utf8()
        return { "status": result[1], "body": JSON.parse_string(text) if text != "" else null }
    
    func exchange_identity_token(provider_token: String) -> bool:
        var r = await _call(HTTPClient.METHOD_POST, "/v1/customer_sessions", {
            "grant_type": "identity_token",
            "connection_id": connection_id,
            "provider_token": provider_token,
        }, false)
        token = r.body.customer_token if r.status == 201 else ""
        return token != ""
    
    # A session your game server minted (grant_type secret_key, see Web game step 5).
    func use_server_session(server_token: String) -> void:
        token = server_token
    
    func read(path: String) -> Dictionary:  # "levels", "balances", "streaks", "quests", ...
        return await _call(HTTPClient.METHOD_GET, "/v1/customer/" + path)
    
    func track(event_name: String, properties := {}, id := "") -> Dictionary:
        return await _call(HTTPClient.METHOD_POST, "/v1/customer/events", {
            "id": id if id != "" else _uuid(), "name": event_name, "properties": properties,
        })
    
    func _uuid() -> String:
        var b := Crypto.new().generate_random_bytes(16)
        b[6] = (b[6] & 0x0f) | 0x40
        b[8] = (b[8] & 0x3f) | 0x80
        var h := b.hex_encode()
        return "%s-%s-%s-%s-%s" % [h.substr(0, 8), h.substr(8, 4), h.substr(12, 4), h.substr(16, 4), h.substr(20, 12)]

    On a 401 get a new session (from your server, or exchange the provider token again) and retry once with the same key. Your game server authenticates the player with the game's own login (a session cookie or token your server issued); never trust a player id the client sends. A retry of the same intent must reuse its Idempotency-Key and event id: pass them in instead of generating new ones when you retry.

  4. What the game sends. session.started once a day when the game opens. With no server, also match.completed ({"score": n, "won": true}) and level.completed ({"level": n}), counted up to the per-day caps. With a server, the server reports matches with the secret key (Web game, step 4).

  5. HUD and player actions: Game setup, player screens.

  6. Test the exact calls from a terminal against the test environment (Game setup, test), then run the game. A desktop or mobile export sends no Origin; a web export does and needs its origin allowed (Web game, step 7).

  7. Go live: Game setup, go live.

On this page