horizon-mcp 0.0.0-development
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/debug-connection.d.ts +3 -0
- package/dist/prompts/debug-connection.d.ts.map +1 -0
- package/dist/prompts/debug-connection.js +17 -0
- package/dist/prompts/debug-connection.js.map +1 -0
- package/dist/prompts/explain-feature.d.ts +3 -0
- package/dist/prompts/explain-feature.d.ts.map +1 -0
- package/dist/prompts/explain-feature.js +32 -0
- package/dist/prompts/explain-feature.js.map +1 -0
- package/dist/prompts/index.d.ts +6 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +14 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/integrate-feature.d.ts +3 -0
- package/dist/prompts/integrate-feature.d.ts.map +1 -0
- package/dist/prompts/integrate-feature.js +35 -0
- package/dist/prompts/integrate-feature.js.map +1 -0
- package/dist/prompts/setup-auth.d.ts +3 -0
- package/dist/prompts/setup-auth.d.ts.map +1 -0
- package/dist/prompts/setup-auth.js +26 -0
- package/dist/prompts/setup-auth.js.map +1 -0
- package/dist/resources/index.d.ts +6 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +208 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +15 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/__tests__/api-client.test.d.ts +2 -0
- package/dist/tools/__tests__/api-client.test.d.ts.map +1 -0
- package/dist/tools/__tests__/api-client.test.js +156 -0
- package/dist/tools/__tests__/api-client.test.js.map +1 -0
- package/dist/tools/api-client.d.ts +25 -0
- package/dist/tools/api-client.d.ts.map +1 -0
- package/dist/tools/api-client.js +78 -0
- package/dist/tools/api-client.js.map +1 -0
- package/dist/tools/auth.d.ts +3 -0
- package/dist/tools/auth.d.ts.map +1 -0
- package/dist/tools/auth.js +123 -0
- package/dist/tools/auth.js.map +1 -0
- package/dist/tools/cloud-save.d.ts +3 -0
- package/dist/tools/cloud-save.d.ts.map +1 -0
- package/dist/tools/cloud-save.js +50 -0
- package/dist/tools/cloud-save.js.map +1 -0
- package/dist/tools/connection.d.ts +3 -0
- package/dist/tools/connection.d.ts.map +1 -0
- package/dist/tools/connection.js +20 -0
- package/dist/tools/connection.js.map +1 -0
- package/dist/tools/feedback.d.ts +3 -0
- package/dist/tools/feedback.d.ts.map +1 -0
- package/dist/tools/feedback.js +36 -0
- package/dist/tools/feedback.js.map +1 -0
- package/dist/tools/gift-codes.d.ts +3 -0
- package/dist/tools/gift-codes.d.ts.map +1 -0
- package/dist/tools/gift-codes.js +52 -0
- package/dist/tools/gift-codes.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +24 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/leaderboard.d.ts +3 -0
- package/dist/tools/leaderboard.d.ts.map +1 -0
- package/dist/tools/leaderboard.js +96 -0
- package/dist/tools/leaderboard.js.map +1 -0
- package/dist/tools/news.d.ts +3 -0
- package/dist/tools/news.d.ts.map +1 -0
- package/dist/tools/news.js +29 -0
- package/dist/tools/news.js.map +1 -0
- package/dist/tools/remote-config.d.ts +3 -0
- package/dist/tools/remote-config.d.ts.map +1 -0
- package/dist/tools/remote-config.js +42 -0
- package/dist/tools/remote-config.js.map +1 -0
- package/dist/tools/tool-helpers.d.ts +24 -0
- package/dist/tools/tool-helpers.d.ts.map +1 -0
- package/dist/tools/tool-helpers.js +54 -0
- package/dist/tools/tool-helpers.js.map +1 -0
- package/dist/tools/user-logs.d.ts +3 -0
- package/dist/tools/user-logs.d.ts.map +1 -0
- package/dist/tools/user-logs.js +30 -0
- package/dist/tools/user-logs.js.map +1 -0
- package/package.json +52 -0
- package/src/resources/api/app-api.md +495 -0
- package/src/resources/docs/auth.md +280 -0
- package/src/resources/docs/cloud-save.md +180 -0
- package/src/resources/docs/feedback.md +126 -0
- package/src/resources/docs/gift-codes.md +153 -0
- package/src/resources/docs/leaderboard.md +201 -0
- package/src/resources/docs/news.md +114 -0
- package/src/resources/docs/overview.md +80 -0
- package/src/resources/docs/remote-config.md +149 -0
- package/src/resources/docs/user-logs.md +144 -0
- package/src/resources/quickstart/godot.md +224 -0
- package/src/resources/quickstart/unity.md +317 -0
- package/src/resources/quickstart/unreal.md +390 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Gift Codes
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Gift Codes allow you to create promotional codes that players can redeem for in-game rewards. Codes are created in the horizOn Dashboard with associated reward data. Each code can be single-use or multi-use, and can optionally have an expiration date.
|
|
6
|
+
|
|
7
|
+
The typical flow is:
|
|
8
|
+
|
|
9
|
+
1. **Validate** (optional) — Check if a code is valid before redeeming
|
|
10
|
+
2. **Redeem** — Redeem the code to receive the reward data
|
|
11
|
+
|
|
12
|
+
## Endpoints
|
|
13
|
+
|
|
14
|
+
### Validate Gift Code
|
|
15
|
+
|
|
16
|
+
**`POST /api/v1/app/gift-codes/validate`**
|
|
17
|
+
|
|
18
|
+
Checks if a gift code is valid and can be redeemed by the user, without consuming it.
|
|
19
|
+
|
|
20
|
+
**Request Body:**
|
|
21
|
+
|
|
22
|
+
| Field | Type | Required | Description |
|
|
23
|
+
|-------|------|----------|-------------|
|
|
24
|
+
| `code` | string | Yes | The gift code to validate |
|
|
25
|
+
| `userId` | string | Yes | The user's ID |
|
|
26
|
+
|
|
27
|
+
**Response (200):**
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"valid": true
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`valid` is `false` if the code does not exist, is expired, or has already been redeemed by this user.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
### Redeem Gift Code
|
|
40
|
+
|
|
41
|
+
**`POST /api/v1/app/gift-codes/redeem`**
|
|
42
|
+
|
|
43
|
+
Redeems a gift code and returns the associated reward data.
|
|
44
|
+
|
|
45
|
+
**Request Body:**
|
|
46
|
+
|
|
47
|
+
| Field | Type | Required | Description |
|
|
48
|
+
|-------|------|----------|-------------|
|
|
49
|
+
| `code` | string | Yes | The gift code to redeem |
|
|
50
|
+
| `userId` | string | Yes | The user's ID |
|
|
51
|
+
|
|
52
|
+
**Response (200):**
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"success": true,
|
|
57
|
+
"message": "Gift code redeemed successfully",
|
|
58
|
+
"giftData": "{\"coins\": 500, \"gems\": 10}"
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`giftData` is a JSON string set by the developer in the Dashboard. Your app must parse and apply the rewards.
|
|
63
|
+
|
|
64
|
+
**Failed redemption:**
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"success": false,
|
|
69
|
+
"message": "Gift code already redeemed by this user",
|
|
70
|
+
"giftData": null
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Code Examples
|
|
75
|
+
|
|
76
|
+
### Godot (GDScript)
|
|
77
|
+
|
|
78
|
+
```gdscript
|
|
79
|
+
# Validate a code first (optional)
|
|
80
|
+
var is_valid = await Horizon.giftCodes.validate("ABCD-1234")
|
|
81
|
+
if is_valid:
|
|
82
|
+
print("Code is valid!")
|
|
83
|
+
|
|
84
|
+
# Redeem a code
|
|
85
|
+
var result = await Horizon.giftCodes.redeem("ABCD-1234")
|
|
86
|
+
if result.get("success", false):
|
|
87
|
+
var gift_data = result.get("giftData", "")
|
|
88
|
+
print("Rewards: %s" % gift_data)
|
|
89
|
+
|
|
90
|
+
# Redeem and auto-parse rewards as Dictionary
|
|
91
|
+
var parsed = await Horizon.giftCodes.redeemParsed("ABCD-1234")
|
|
92
|
+
if parsed.get("success", false):
|
|
93
|
+
var rewards: Dictionary = parsed.get("rewards", {})
|
|
94
|
+
var coins = rewards.get("coins", 0)
|
|
95
|
+
var gems = rewards.get("gems", 0)
|
|
96
|
+
|
|
97
|
+
# Listen for events
|
|
98
|
+
Horizon.giftCodes.code_validated.connect(func(code, valid): print("%s: %s" % [code, "valid" if valid else "invalid"]))
|
|
99
|
+
Horizon.giftCodes.code_redeemed.connect(func(code, data): print("Redeemed %s: %s" % [code, data]))
|
|
100
|
+
Horizon.giftCodes.code_redeem_failed.connect(func(code, error): print("Failed: %s" % error))
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Unity (C#)
|
|
104
|
+
|
|
105
|
+
```csharp
|
|
106
|
+
using PM.horizOn.Cloud.Manager;
|
|
107
|
+
|
|
108
|
+
// Validate (optional)
|
|
109
|
+
bool? valid = await GiftCodeManager.Instance.Validate("PROMO2024");
|
|
110
|
+
if (valid == true)
|
|
111
|
+
{
|
|
112
|
+
Debug.Log("Code is valid!");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Redeem
|
|
116
|
+
var result = await GiftCodeManager.Instance.Redeem("PROMO2024");
|
|
117
|
+
if (result != null && result.success)
|
|
118
|
+
{
|
|
119
|
+
// Parse giftData JSON for rewards
|
|
120
|
+
string giftData = result.giftData;
|
|
121
|
+
Debug.Log($"Rewards: {giftData}");
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### REST (cURL)
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# Validate
|
|
129
|
+
curl -X POST https://horizon.pm/api/v1/app/gift-codes/validate \
|
|
130
|
+
-H "X-API-Key: YOUR_API_KEY" \
|
|
131
|
+
-H "Content-Type: application/json" \
|
|
132
|
+
-d '{"code": "ABCD-1234", "userId": "user123"}'
|
|
133
|
+
|
|
134
|
+
# Redeem
|
|
135
|
+
curl -X POST https://horizon.pm/api/v1/app/gift-codes/redeem \
|
|
136
|
+
-H "X-API-Key: YOUR_API_KEY" \
|
|
137
|
+
-H "Content-Type: application/json" \
|
|
138
|
+
-d '{"code": "ABCD-1234", "userId": "user123"}'
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Best Practices
|
|
142
|
+
|
|
143
|
+
- **Validate before redeeming** — Show the user whether their code is valid before consuming it.
|
|
144
|
+
- **Parse giftData in your app** — The reward structure is defined by you in the Dashboard. Parse the JSON string and apply rewards accordingly.
|
|
145
|
+
- **Handle already-redeemed codes** — Check the `success` field and display the `message` to the user.
|
|
146
|
+
|
|
147
|
+
## Common Errors
|
|
148
|
+
|
|
149
|
+
| Status | Cause | Solution |
|
|
150
|
+
|--------|-------|----------|
|
|
151
|
+
| 401 | Invalid API key | Check `X-API-Key` header |
|
|
152
|
+
| 404 | Code does not exist | Verify the code spelling |
|
|
153
|
+
| 429 | Rate limit exceeded | Avoid rapid repeated redemption attempts |
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Leaderboards
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
horizOn leaderboards provide global rankings for your app. Scores are submitted per user and **only update if the new score is higher** than the user's previous best. This ensures leaderboards always reflect each player's best achievement.
|
|
6
|
+
|
|
7
|
+
## Endpoints
|
|
8
|
+
|
|
9
|
+
### Submit Score
|
|
10
|
+
|
|
11
|
+
**`POST /api/v1/app/leaderboard/submit`**
|
|
12
|
+
|
|
13
|
+
Submits a score for the authenticated user. Only updates if the score is higher than the previous best.
|
|
14
|
+
|
|
15
|
+
**Request Body:**
|
|
16
|
+
|
|
17
|
+
| Field | Type | Required | Description |
|
|
18
|
+
|-------|------|----------|-------------|
|
|
19
|
+
| `userId` | string | Yes | The user's ID |
|
|
20
|
+
| `score` | number | Yes | Score value (positive integer) |
|
|
21
|
+
|
|
22
|
+
**Response (200):**
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"success": true
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
### Get Top Entries
|
|
33
|
+
|
|
34
|
+
**`GET /api/v1/app/leaderboard/top?userId={userId}&limit={limit}`**
|
|
35
|
+
|
|
36
|
+
Returns the top entries on the leaderboard.
|
|
37
|
+
|
|
38
|
+
**Query Parameters:**
|
|
39
|
+
|
|
40
|
+
| Param | Type | Required | Description |
|
|
41
|
+
|-------|------|----------|-------------|
|
|
42
|
+
| `userId` | string | Yes | The requesting user's ID |
|
|
43
|
+
| `limit` | number | No | Number of entries (default 10, max 100) |
|
|
44
|
+
|
|
45
|
+
**Response (200):**
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"entries": [
|
|
50
|
+
{ "position": 1, "username": "TopPlayer", "score": 50000 },
|
|
51
|
+
{ "position": 2, "username": "Runner-Up", "score": 45000 },
|
|
52
|
+
{ "position": 3, "username": "ThirdPlace", "score": 40000 }
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
### Get User Rank
|
|
60
|
+
|
|
61
|
+
**`GET /api/v1/app/leaderboard/rank?userId={userId}`**
|
|
62
|
+
|
|
63
|
+
Returns the current user's position on the leaderboard.
|
|
64
|
+
|
|
65
|
+
**Query Parameters:**
|
|
66
|
+
|
|
67
|
+
| Param | Type | Required | Description |
|
|
68
|
+
|-------|------|----------|-------------|
|
|
69
|
+
| `userId` | string | Yes | The user's ID |
|
|
70
|
+
|
|
71
|
+
**Response (200):**
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"position": 42,
|
|
76
|
+
"username": "MyPlayer",
|
|
77
|
+
"score": 12500
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
### Get Entries Around User
|
|
84
|
+
|
|
85
|
+
**`GET /api/v1/app/leaderboard/around?userId={userId}&range={range}`**
|
|
86
|
+
|
|
87
|
+
Returns entries around the user's position (players ranked near them).
|
|
88
|
+
|
|
89
|
+
**Query Parameters:**
|
|
90
|
+
|
|
91
|
+
| Param | Type | Required | Description |
|
|
92
|
+
|-------|------|----------|-------------|
|
|
93
|
+
| `userId` | string | Yes | The user's ID |
|
|
94
|
+
| `range` | number | No | Number of entries before and after (default 10) |
|
|
95
|
+
|
|
96
|
+
**Response (200):**
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"entries": [
|
|
101
|
+
{ "position": 40, "username": "NearbyPlayer1", "score": 13000 },
|
|
102
|
+
{ "position": 41, "username": "NearbyPlayer2", "score": 12800 },
|
|
103
|
+
{ "position": 42, "username": "MyPlayer", "score": 12500 },
|
|
104
|
+
{ "position": 43, "username": "NearbyPlayer3", "score": 12200 }
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Code Examples
|
|
110
|
+
|
|
111
|
+
### Godot (GDScript)
|
|
112
|
+
|
|
113
|
+
```gdscript
|
|
114
|
+
# Submit a score (only updates if higher than previous best)
|
|
115
|
+
await Horizon.leaderboard.submitScore(1000)
|
|
116
|
+
|
|
117
|
+
# Get top 10 players
|
|
118
|
+
var top: Array[HorizonLeaderboardEntry] = await Horizon.leaderboard.getTop(10)
|
|
119
|
+
for entry in top:
|
|
120
|
+
print("#%d %s: %d" % [entry.position, entry.username, entry.score])
|
|
121
|
+
|
|
122
|
+
# Get current user's rank
|
|
123
|
+
var myRank: HorizonLeaderboardEntry = await Horizon.leaderboard.getRank()
|
|
124
|
+
print("My rank: #%d (Score: %d)" % [myRank.position, myRank.score])
|
|
125
|
+
|
|
126
|
+
# Get entries around the user's position
|
|
127
|
+
var around: Array[HorizonLeaderboardEntry] = await Horizon.leaderboard.getAround(5)
|
|
128
|
+
|
|
129
|
+
# Use caching (enabled by default)
|
|
130
|
+
var cached_top = await Horizon.leaderboard.getTop(10, true) # uses cache
|
|
131
|
+
var fresh_top = await Horizon.leaderboard.getTop(10, false) # forces refresh
|
|
132
|
+
|
|
133
|
+
# Clear cache manually
|
|
134
|
+
Horizon.leaderboard.clearCache()
|
|
135
|
+
|
|
136
|
+
# Listen for events
|
|
137
|
+
Horizon.leaderboard.score_submitted.connect(func(score): print("Submitted: %d" % score))
|
|
138
|
+
Horizon.leaderboard.top_entries_loaded.connect(func(entries): print("Loaded %d entries" % entries.size()))
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Unity (C#)
|
|
142
|
+
|
|
143
|
+
```csharp
|
|
144
|
+
using PM.horizOn.Cloud.Manager;
|
|
145
|
+
|
|
146
|
+
// Submit score
|
|
147
|
+
await LeaderboardManager.Instance.SubmitScore(12500);
|
|
148
|
+
|
|
149
|
+
// Get top 10 players
|
|
150
|
+
var top = await LeaderboardManager.Instance.GetTop(10);
|
|
151
|
+
foreach (var entry in top)
|
|
152
|
+
{
|
|
153
|
+
Debug.Log($"#{entry.position} {entry.username}: {entry.score}");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Get your rank
|
|
157
|
+
var rank = await LeaderboardManager.Instance.GetRank();
|
|
158
|
+
Debug.Log($"My rank: #{rank.position} (Score: {rank.score})");
|
|
159
|
+
|
|
160
|
+
// Get entries around user
|
|
161
|
+
var around = await LeaderboardManager.Instance.GetAround(5);
|
|
162
|
+
|
|
163
|
+
// Clear cache
|
|
164
|
+
LeaderboardManager.Instance.ClearCache();
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### REST (cURL)
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# Submit score
|
|
171
|
+
curl -X POST https://horizon.pm/api/v1/app/leaderboard/submit \
|
|
172
|
+
-H "X-API-Key: YOUR_API_KEY" \
|
|
173
|
+
-H "Content-Type: application/json" \
|
|
174
|
+
-d '{"userId": "user123", "score": 12500}'
|
|
175
|
+
|
|
176
|
+
# Get top 10
|
|
177
|
+
curl "https://horizon.pm/api/v1/app/leaderboard/top?userId=user123&limit=10" \
|
|
178
|
+
-H "X-API-Key: YOUR_API_KEY"
|
|
179
|
+
|
|
180
|
+
# Get rank
|
|
181
|
+
curl "https://horizon.pm/api/v1/app/leaderboard/rank?userId=user123" \
|
|
182
|
+
-H "X-API-Key: YOUR_API_KEY"
|
|
183
|
+
|
|
184
|
+
# Get around
|
|
185
|
+
curl "https://horizon.pm/api/v1/app/leaderboard/around?userId=user123&range=5" \
|
|
186
|
+
-H "X-API-Key: YOUR_API_KEY"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Best Practices
|
|
190
|
+
|
|
191
|
+
- **Only submit on new high score** — The server already enforces "highest wins," but avoiding unnecessary requests saves your rate limit quota.
|
|
192
|
+
- **Cache leaderboard data** — Both SDKs cache by default. Use cached data for display and refresh periodically.
|
|
193
|
+
- **Limit query size** — Request only as many entries as you display (e.g., top 10, not top 100).
|
|
194
|
+
|
|
195
|
+
## Common Errors
|
|
196
|
+
|
|
197
|
+
| Status | Cause | Solution |
|
|
198
|
+
|--------|-------|----------|
|
|
199
|
+
| 401 | Invalid API key | Check `X-API-Key` header |
|
|
200
|
+
| 404 | User not found on leaderboard | User may not have submitted a score yet |
|
|
201
|
+
| 429 | Rate limit exceeded | Cache data and reduce API calls |
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# News
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The News feature lets you display in-game announcements, patch notes, and updates to your players. News entries are created in the horizOn Dashboard and fetched by the app at runtime. Entries can be filtered by language.
|
|
6
|
+
|
|
7
|
+
## Endpoints
|
|
8
|
+
|
|
9
|
+
### Get News
|
|
10
|
+
|
|
11
|
+
**`GET /api/v1/app/news?limit={limit}&languageCode={languageCode}`**
|
|
12
|
+
|
|
13
|
+
Returns a list of published news entries, ordered by release date (newest first).
|
|
14
|
+
|
|
15
|
+
**Query Parameters:**
|
|
16
|
+
|
|
17
|
+
| Param | Type | Required | Description |
|
|
18
|
+
|-------|------|----------|-------------|
|
|
19
|
+
| `limit` | number | No | Max entries to return (default 20, max 100) |
|
|
20
|
+
| `languageCode` | string | No | Filter by language (e.g., `en`, `de`, `fr`) |
|
|
21
|
+
|
|
22
|
+
**Response (200):**
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
[
|
|
26
|
+
{
|
|
27
|
+
"id": "news-001",
|
|
28
|
+
"title": "Version 2.0 Released!",
|
|
29
|
+
"message": "We've added new features including cloud saves and leaderboards.",
|
|
30
|
+
"releaseDate": "2025-01-15T12:00:00Z",
|
|
31
|
+
"languageCode": "en"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "news-002",
|
|
35
|
+
"title": "Holiday Event",
|
|
36
|
+
"message": "Earn double XP during the holiday season!",
|
|
37
|
+
"releaseDate": "2025-01-10T08:00:00Z",
|
|
38
|
+
"languageCode": "en"
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Code Examples
|
|
44
|
+
|
|
45
|
+
### Godot (GDScript)
|
|
46
|
+
|
|
47
|
+
```gdscript
|
|
48
|
+
# Load latest 20 news entries
|
|
49
|
+
var news: Array[HorizonNewsEntry] = await Horizon.news.loadNews()
|
|
50
|
+
|
|
51
|
+
# Load with filters
|
|
52
|
+
var english_news = await Horizon.news.loadNews(10, "en")
|
|
53
|
+
|
|
54
|
+
# Iterate over entries
|
|
55
|
+
for entry in news:
|
|
56
|
+
print("%s: %s" % [entry.title, entry.message])
|
|
57
|
+
print(" Date: %s | Language: %s" % [entry.releaseDate, entry.languageCode])
|
|
58
|
+
|
|
59
|
+
# Caching (enabled by default)
|
|
60
|
+
var cached = await Horizon.news.loadNews(20, "", true) # uses cache
|
|
61
|
+
var fresh = await Horizon.news.loadNews(20, "", false) # forces refresh
|
|
62
|
+
|
|
63
|
+
# Get cached news without network request
|
|
64
|
+
var cached_entries = Horizon.news.getCachedNews()
|
|
65
|
+
|
|
66
|
+
# Clear cache
|
|
67
|
+
Horizon.news.clearCache()
|
|
68
|
+
|
|
69
|
+
# Listen for events
|
|
70
|
+
Horizon.news.news_loaded.connect(func(entries): print("Loaded %d entries" % entries.size()))
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Unity (C#)
|
|
74
|
+
|
|
75
|
+
```csharp
|
|
76
|
+
using PM.horizOn.Cloud.Manager;
|
|
77
|
+
|
|
78
|
+
// Load news
|
|
79
|
+
var news = await NewsManager.Instance.LoadNews(limit: 10);
|
|
80
|
+
foreach (var item in news)
|
|
81
|
+
{
|
|
82
|
+
Debug.Log($"{item.title}: {item.message}");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Filter by language
|
|
86
|
+
var germanNews = await NewsManager.Instance.LoadNews(limit: 10, languageCode: "de");
|
|
87
|
+
|
|
88
|
+
// Get specific entry from cache
|
|
89
|
+
var entry = NewsManager.Instance.GetNewsById("news-001");
|
|
90
|
+
|
|
91
|
+
// Clear cache
|
|
92
|
+
NewsManager.Instance.ClearCache();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### REST (cURL)
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Get latest 10 news entries in English
|
|
99
|
+
curl "https://horizon.pm/api/v1/app/news?limit=10&languageCode=en" \
|
|
100
|
+
-H "X-API-Key: YOUR_API_KEY"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Best Practices
|
|
104
|
+
|
|
105
|
+
- **Fetch news at startup** — Load news once when the app starts and display in a news panel.
|
|
106
|
+
- **Use language filtering** — Show news in the user's preferred language.
|
|
107
|
+
- **Cache results** — News does not change frequently; cache for the session duration.
|
|
108
|
+
|
|
109
|
+
## Common Errors
|
|
110
|
+
|
|
111
|
+
| Status | Cause | Solution |
|
|
112
|
+
|--------|-------|----------|
|
|
113
|
+
| 401 | Invalid API key | Check `X-API-Key` header |
|
|
114
|
+
| 429 | Rate limit exceeded | Cache news data, fetch once at startup |
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# horizOn Overview
|
|
2
|
+
|
|
3
|
+
## What is horizOn?
|
|
4
|
+
|
|
5
|
+
horizOn is a **multi-tenant Backend-as-a-Service (BaaS)** designed for game and app developers. It provides ready-to-use backend features so developers can focus on building their games instead of managing servers.
|
|
6
|
+
|
|
7
|
+
**Base URL:** `https://horizon.pm`
|
|
8
|
+
|
|
9
|
+
## Key Concept: Account vs User
|
|
10
|
+
|
|
11
|
+
This distinction is critical when working with horizOn:
|
|
12
|
+
|
|
13
|
+
- **Account** — The developer or company using horizOn. An account owns one or more apps, manages API keys, and configures features via the horizOn Dashboard.
|
|
14
|
+
- **User** — The end-user of the developer's app (a player). Users sign up, sign in, save data, submit scores, etc.
|
|
15
|
+
|
|
16
|
+
The App API (used by SDKs and this MCP server) operates on behalf of **Users** within a developer's **Account**. The `X-API-Key` header identifies which Account/App the request belongs to.
|
|
17
|
+
|
|
18
|
+
## Core Features
|
|
19
|
+
|
|
20
|
+
horizOn provides 8 core features:
|
|
21
|
+
|
|
22
|
+
| # | Feature | Description |
|
|
23
|
+
|---|---------|-------------|
|
|
24
|
+
| 1 | **Authentication** | Anonymous, email/password, and Google OAuth sign-in/sign-up |
|
|
25
|
+
| 2 | **Cloud Save** | Persist player data across devices (JSON or binary) |
|
|
26
|
+
| 3 | **Leaderboards** | Global rankings with score submission and queries |
|
|
27
|
+
| 4 | **Remote Config** | Server-side key-value configuration (feature flags, balancing) |
|
|
28
|
+
| 5 | **News** | In-game news and announcements with language filtering |
|
|
29
|
+
| 6 | **Gift Codes** | Promotional code validation and redemption |
|
|
30
|
+
| 7 | **User Feedback** | Bug reports, feature requests, and general feedback |
|
|
31
|
+
| 8 | **User Logs** | Server-side event and error tracking per user |
|
|
32
|
+
|
|
33
|
+
## Tier System
|
|
34
|
+
|
|
35
|
+
horizOn offers four pricing tiers with different limits:
|
|
36
|
+
|
|
37
|
+
| Feature | FREE | BASIC | PRO | ENTERPRISE |
|
|
38
|
+
|---------|------|-------|-----|------------|
|
|
39
|
+
| **Cloud Save Size** | 1 KB | 5 KB | 20 KB | 250 KB |
|
|
40
|
+
| **User Logs** | Not available | Available | Available | Available |
|
|
41
|
+
| **Rate Limit** | 10 req/min | 10 req/min | 10 req/min | 10 req/min |
|
|
42
|
+
| **All other features** | Available | Available | Available | Available |
|
|
43
|
+
|
|
44
|
+
**Rate Limit:** All tiers are limited to **10 requests per minute per client**. Plan API calls carefully and use caching.
|
|
45
|
+
|
|
46
|
+
## API Structure
|
|
47
|
+
|
|
48
|
+
All App API endpoints follow the pattern:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
{METHOD} /api/v1/app/{feature}/{action}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Every request requires the `X-API-Key` header:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
X-API-Key: your-api-key-here
|
|
58
|
+
Content-Type: application/json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## SDKs
|
|
62
|
+
|
|
63
|
+
| Engine | SDK | Language |
|
|
64
|
+
|--------|-----|----------|
|
|
65
|
+
| **Godot** | [horizOn SDK for Godot](https://github.com/ProjectMakersDE/horizOn-SDK-Godot) | GDScript |
|
|
66
|
+
| **Unity** | [horizOn Cloud SDK for Unity](https://github.com/ProjectMakersDE/horizOn-SDK-Unity) | C# |
|
|
67
|
+
| **Unreal Engine** | No official SDK | Use REST API directly (C++ FHttpModule or VaRest plugin) |
|
|
68
|
+
|
|
69
|
+
## Common HTTP Status Codes
|
|
70
|
+
|
|
71
|
+
| Code | Meaning | Action |
|
|
72
|
+
|------|---------|--------|
|
|
73
|
+
| 200 | Success | Request completed successfully |
|
|
74
|
+
| 400 | Bad Request | Check request parameters |
|
|
75
|
+
| 401 | Unauthorized | Invalid or missing API key |
|
|
76
|
+
| 403 | Forbidden | Feature not available for tier, or user not verified |
|
|
77
|
+
| 404 | Not Found | Resource does not exist |
|
|
78
|
+
| 409 | Conflict | User already exists (signup) |
|
|
79
|
+
| 429 | Too Many Requests | Rate limit exceeded, wait and retry |
|
|
80
|
+
| 500 | Server Error | Internal server error, retry later |
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Remote Config
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Remote Config lets you store key-value pairs on the server that your app can fetch at runtime. This is useful for:
|
|
6
|
+
|
|
7
|
+
- **Feature flags** — Enable/disable features without an app update
|
|
8
|
+
- **Game balance** — Tweak difficulty, rewards, or economy values
|
|
9
|
+
- **A/B testing** — Serve different values to different users
|
|
10
|
+
- **Maintenance mode** — Toggle server maintenance messages
|
|
11
|
+
- **Version gating** — Enforce minimum app versions
|
|
12
|
+
|
|
13
|
+
Config values are set in the horizOn Dashboard and are read-only from the app side.
|
|
14
|
+
|
|
15
|
+
## Endpoints
|
|
16
|
+
|
|
17
|
+
### Get Single Config
|
|
18
|
+
|
|
19
|
+
**`GET /api/v1/app/remote-config/{configKey}`**
|
|
20
|
+
|
|
21
|
+
Retrieves a single configuration value by its key.
|
|
22
|
+
|
|
23
|
+
**Path Parameters:**
|
|
24
|
+
|
|
25
|
+
| Param | Type | Required | Description |
|
|
26
|
+
|-------|------|----------|-------------|
|
|
27
|
+
| `configKey` | string | Yes | The configuration key |
|
|
28
|
+
|
|
29
|
+
**Response (200):**
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"configKey": "max_level",
|
|
34
|
+
"configValue": "50",
|
|
35
|
+
"found": true
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If the key does not exist:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"configKey": "unknown_key",
|
|
44
|
+
"configValue": null,
|
|
45
|
+
"found": false
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### Get All Configs
|
|
52
|
+
|
|
53
|
+
**`GET /api/v1/app/remote-config/all`**
|
|
54
|
+
|
|
55
|
+
Retrieves all configuration values for the app.
|
|
56
|
+
|
|
57
|
+
**Response (200):**
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"configs": {
|
|
62
|
+
"max_level": "50",
|
|
63
|
+
"difficulty": "1.5",
|
|
64
|
+
"maintenance_mode": "false",
|
|
65
|
+
"welcome_message": "Hello, Player!"
|
|
66
|
+
},
|
|
67
|
+
"total": 4
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Code Examples
|
|
72
|
+
|
|
73
|
+
### Godot (GDScript)
|
|
74
|
+
|
|
75
|
+
```gdscript
|
|
76
|
+
# Get a single config value
|
|
77
|
+
var version: String = await Horizon.remoteConfig.getConfig("game_version")
|
|
78
|
+
|
|
79
|
+
# Typed getters with defaults
|
|
80
|
+
var maxLevel: int = await Horizon.remoteConfig.getInt("max_level", 100)
|
|
81
|
+
var difficulty: float = await Horizon.remoteConfig.getFloat("difficulty", 1.0)
|
|
82
|
+
var maintenance: bool = await Horizon.remoteConfig.getBool("maintenance_mode", false)
|
|
83
|
+
|
|
84
|
+
# Parse JSON config values
|
|
85
|
+
var jsonData = await Horizon.remoteConfig.getJson("event_config")
|
|
86
|
+
|
|
87
|
+
# Get all configs at once (recommended at startup)
|
|
88
|
+
var all: Dictionary = await Horizon.remoteConfig.getAllConfigs()
|
|
89
|
+
|
|
90
|
+
# Caching (enabled by default)
|
|
91
|
+
var cached = await Horizon.remoteConfig.getConfig("key", true) # uses cache
|
|
92
|
+
var fresh = await Horizon.remoteConfig.getConfig("key", false) # forces refresh
|
|
93
|
+
|
|
94
|
+
# Check if a key exists
|
|
95
|
+
var exists: bool = await Horizon.remoteConfig.hasKey("some_key")
|
|
96
|
+
|
|
97
|
+
# Clear cache
|
|
98
|
+
Horizon.remoteConfig.clearCache()
|
|
99
|
+
|
|
100
|
+
# Listen for events
|
|
101
|
+
Horizon.remoteConfig.config_loaded.connect(func(key, value): print("%s = %s" % [key, value]))
|
|
102
|
+
Horizon.remoteConfig.all_configs_loaded.connect(func(configs): print("Loaded %d configs" % configs.size()))
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Unity (C#)
|
|
106
|
+
|
|
107
|
+
```csharp
|
|
108
|
+
using PM.horizOn.Cloud.Manager;
|
|
109
|
+
|
|
110
|
+
// Get a single config
|
|
111
|
+
string value = await RemoteConfigManager.Instance.GetConfig("game_version");
|
|
112
|
+
|
|
113
|
+
// Type-safe getters with defaults
|
|
114
|
+
int maxLives = await RemoteConfigManager.Instance.GetInt("max_lives", 3);
|
|
115
|
+
float difficulty = await RemoteConfigManager.Instance.GetFloat("difficulty", 1.0f);
|
|
116
|
+
bool eventActive = await RemoteConfigManager.Instance.GetBool("holiday_event", false);
|
|
117
|
+
|
|
118
|
+
// Get all configs at once (recommended at startup)
|
|
119
|
+
var configs = await RemoteConfigManager.Instance.GetAllConfigs();
|
|
120
|
+
|
|
121
|
+
// Clear cache
|
|
122
|
+
RemoteConfigManager.Instance.ClearCache();
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### REST (cURL)
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# Get single config
|
|
129
|
+
curl "https://horizon.pm/api/v1/app/remote-config/max_level" \
|
|
130
|
+
-H "X-API-Key: YOUR_API_KEY"
|
|
131
|
+
|
|
132
|
+
# Get all configs
|
|
133
|
+
curl "https://horizon.pm/api/v1/app/remote-config/all" \
|
|
134
|
+
-H "X-API-Key: YOUR_API_KEY"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Best Practices
|
|
138
|
+
|
|
139
|
+
- **Fetch all configs at startup** — Use `getAllConfigs()` once on app launch and rely on the cache.
|
|
140
|
+
- **Provide sensible defaults** — Always pass default values in typed getters in case the config key is missing.
|
|
141
|
+
- **Cache aggressively** — Config values rarely change during a session.
|
|
142
|
+
- **Avoid frequent polling** — Config changes take effect when users restart the app or you explicitly refresh.
|
|
143
|
+
|
|
144
|
+
## Common Errors
|
|
145
|
+
|
|
146
|
+
| Status | Cause | Solution |
|
|
147
|
+
|--------|-------|----------|
|
|
148
|
+
| 401 | Invalid API key | Check `X-API-Key` header |
|
|
149
|
+
| 429 | Rate limit exceeded | Cache config values, fetch once at startup |
|