Add leaderboards to your game easily

Manage a leaderboard for your game.

Scoreforge is a multi-tenant leaderboard SaaS built for game developers. Create and manage leaderboards for your games without the hassle of building and maintaining your own backend. Focus on making great games while we handle the leaderboard infrastructure.

Project-based setup

Create one project per game and keep every leaderboard isolated.

Secure API keys

Generate keys for game clients and backend integrations safely.

Fast integration

Connect your game quickly and focus on gameplay instead of backend work.

Dashboard

Welcome back

Login to manage projects, generate API keys and control your leaderboard SaaS.

How to integrate it in Godot

This guide shows the real flow to connect a Godot game with Scoreforge: create a project, generate an API key, submit scores, and read the public leaderboard.

1. Create your project

Open the dashboard, create a project for your game, and note its project ID. Then generate the API key. The API key is shown once, so save it immediately.

2. Submit scores from Godot

Use an HTTPRequest node to send a POST request to Scoreforge with the X-API-Key header. The URL stays the Scoreforge service URL. What changes is your API key and the score data you send.

3. Read the public leaderboard

Use the project ID shown in your dashboard and request the public leaderboard endpoint. You can display the returned JSON directly inside your game UI.

What you need from the dashboard

You need 2 things: the API key for score submission, and the project ID to read the leaderboard.

Submit endpoint

POST /scores/submit with headers Content-Type: application/json and X-API-Key: YOUR_API_KEY

Leaderboard endpoint

GET /scores/leaderboard/YOUR_PROJECT_ID?limit=20 to retrieve the public ranking list.

Godot example: submit score and fetch leaderboard

Keep the Scoreforge base URL as-is. Replace only YOUR_API_KEY_HERE and YOUR_PROJECT_ID_HERE with the values from your dashboard.

scoreforge_example.gd
Godot GDScript
1extends Node
2class_name ScoreforgeClient
3
4@export var base_url: String = "https://scoreforge-8cmh.vercel.app"
5@export var api_key: String = "YOUR_API_KEY_HERE"
6@export var project_id: int = 1
7
8func submit_score(username: String, value: int, metadata: Dictionary = {}) -> Dictionary:
9 if api_key.strip_edges() == "":
10 return {
11 "ok": false,
12 "error": "Missing api_key"
13 }
14
15 if project_id <= 0:
16 return {
17 "ok": false,
18 "error": "Missing project_id"
19 }
20
21 var url = "%s/scores/submit" % base_url
22 var headers = PackedStringArray([
23 "Accept: application/json",
24 "Content-Type: application/json",
25 "X-API-Key: %s" % api_key
26 ])
27
28 var body = {
29 "project_id": project_id,
30 "username": username,
31 "value": value,
32 "metadata": metadata
33 }
34
35 return await _request_json(url, HTTPClient.METHOD_POST, headers, body)
36
37func get_leaderboard(limit: int = 20) -> Dictionary:
38 if project_id <= 0:
39 return {
40 "ok": false,
41 "error": "Missing project_id"
42 }
43
44 var url = "%s/scores/leaderboard/%d?limit=%d" % [base_url, project_id, limit]
45 var headers = PackedStringArray([
46 "Accept: application/json"
47 ])
48
49 return await _request_json(url, HTTPClient.METHOD_GET, headers, null)
50
51func _request_json(url: String, method: int, headers: PackedStringArray, body: Variant) -> Dictionary:
52 var http = HTTPRequest.new()
53 add_child(http)
54
55 var body_text = ""
56 if body != null:
57 body_text = JSON.stringify(body)
58
59 var err = http.request(url, headers, method, body_text)
60 if err != OK:
61 http.queue_free()
62 return {
63 "ok": false,
64 "status": 0,
65 "error": "Request error: %s" % str(err)
66 }
67
68 var response = await http.request_completed
69 http.queue_free()
70
71 var result = response[0]
72 var status = response[1]
73 var raw_body: PackedByteArray = response[3]
74 var text = raw_body.get_string_from_utf8()
75
76 if result != HTTPRequest.RESULT_SUCCESS:
77 return {
78 "ok": false,
79 "status": status,
80 "error": "HTTP request failed",
81 "raw": text
82 }
83
84 if text.strip_edges() == "":
85 return {
86 "ok": status >= 200 and status < 300,
87 "status": status,
88 "data": null
89 }
90
91 var data = JSON.parse_string(text)
92 if data == null:
93 return {
94 "ok": false,
95 "status": status,
96 "error": "Invalid JSON response",
97 "raw": text
98 }
99
100 if status < 200 or status >= 300:
101 return {
102 "ok": false,
103 "status": status,
104 "error": "HTTP error",
105 "data": data
106 }
107
108 return {
109 "ok": true,
110 "status": status,
111 "data": data
112 }