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.
Files changed (99) hide show
  1. package/README.md +138 -0
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +7 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/prompts/debug-connection.d.ts +3 -0
  7. package/dist/prompts/debug-connection.d.ts.map +1 -0
  8. package/dist/prompts/debug-connection.js +17 -0
  9. package/dist/prompts/debug-connection.js.map +1 -0
  10. package/dist/prompts/explain-feature.d.ts +3 -0
  11. package/dist/prompts/explain-feature.d.ts.map +1 -0
  12. package/dist/prompts/explain-feature.js +32 -0
  13. package/dist/prompts/explain-feature.js.map +1 -0
  14. package/dist/prompts/index.d.ts +6 -0
  15. package/dist/prompts/index.d.ts.map +1 -0
  16. package/dist/prompts/index.js +14 -0
  17. package/dist/prompts/index.js.map +1 -0
  18. package/dist/prompts/integrate-feature.d.ts +3 -0
  19. package/dist/prompts/integrate-feature.d.ts.map +1 -0
  20. package/dist/prompts/integrate-feature.js +35 -0
  21. package/dist/prompts/integrate-feature.js.map +1 -0
  22. package/dist/prompts/setup-auth.d.ts +3 -0
  23. package/dist/prompts/setup-auth.d.ts.map +1 -0
  24. package/dist/prompts/setup-auth.js +26 -0
  25. package/dist/prompts/setup-auth.js.map +1 -0
  26. package/dist/resources/index.d.ts +6 -0
  27. package/dist/resources/index.d.ts.map +1 -0
  28. package/dist/resources/index.js +208 -0
  29. package/dist/resources/index.js.map +1 -0
  30. package/dist/server.d.ts +3 -0
  31. package/dist/server.d.ts.map +1 -0
  32. package/dist/server.js +15 -0
  33. package/dist/server.js.map +1 -0
  34. package/dist/tools/__tests__/api-client.test.d.ts +2 -0
  35. package/dist/tools/__tests__/api-client.test.d.ts.map +1 -0
  36. package/dist/tools/__tests__/api-client.test.js +156 -0
  37. package/dist/tools/__tests__/api-client.test.js.map +1 -0
  38. package/dist/tools/api-client.d.ts +25 -0
  39. package/dist/tools/api-client.d.ts.map +1 -0
  40. package/dist/tools/api-client.js +78 -0
  41. package/dist/tools/api-client.js.map +1 -0
  42. package/dist/tools/auth.d.ts +3 -0
  43. package/dist/tools/auth.d.ts.map +1 -0
  44. package/dist/tools/auth.js +123 -0
  45. package/dist/tools/auth.js.map +1 -0
  46. package/dist/tools/cloud-save.d.ts +3 -0
  47. package/dist/tools/cloud-save.d.ts.map +1 -0
  48. package/dist/tools/cloud-save.js +50 -0
  49. package/dist/tools/cloud-save.js.map +1 -0
  50. package/dist/tools/connection.d.ts +3 -0
  51. package/dist/tools/connection.d.ts.map +1 -0
  52. package/dist/tools/connection.js +20 -0
  53. package/dist/tools/connection.js.map +1 -0
  54. package/dist/tools/feedback.d.ts +3 -0
  55. package/dist/tools/feedback.d.ts.map +1 -0
  56. package/dist/tools/feedback.js +36 -0
  57. package/dist/tools/feedback.js.map +1 -0
  58. package/dist/tools/gift-codes.d.ts +3 -0
  59. package/dist/tools/gift-codes.d.ts.map +1 -0
  60. package/dist/tools/gift-codes.js +52 -0
  61. package/dist/tools/gift-codes.js.map +1 -0
  62. package/dist/tools/index.d.ts +6 -0
  63. package/dist/tools/index.d.ts.map +1 -0
  64. package/dist/tools/index.js +24 -0
  65. package/dist/tools/index.js.map +1 -0
  66. package/dist/tools/leaderboard.d.ts +3 -0
  67. package/dist/tools/leaderboard.d.ts.map +1 -0
  68. package/dist/tools/leaderboard.js +96 -0
  69. package/dist/tools/leaderboard.js.map +1 -0
  70. package/dist/tools/news.d.ts +3 -0
  71. package/dist/tools/news.d.ts.map +1 -0
  72. package/dist/tools/news.js +29 -0
  73. package/dist/tools/news.js.map +1 -0
  74. package/dist/tools/remote-config.d.ts +3 -0
  75. package/dist/tools/remote-config.d.ts.map +1 -0
  76. package/dist/tools/remote-config.js +42 -0
  77. package/dist/tools/remote-config.js.map +1 -0
  78. package/dist/tools/tool-helpers.d.ts +24 -0
  79. package/dist/tools/tool-helpers.d.ts.map +1 -0
  80. package/dist/tools/tool-helpers.js +54 -0
  81. package/dist/tools/tool-helpers.js.map +1 -0
  82. package/dist/tools/user-logs.d.ts +3 -0
  83. package/dist/tools/user-logs.d.ts.map +1 -0
  84. package/dist/tools/user-logs.js +30 -0
  85. package/dist/tools/user-logs.js.map +1 -0
  86. package/package.json +52 -0
  87. package/src/resources/api/app-api.md +495 -0
  88. package/src/resources/docs/auth.md +280 -0
  89. package/src/resources/docs/cloud-save.md +180 -0
  90. package/src/resources/docs/feedback.md +126 -0
  91. package/src/resources/docs/gift-codes.md +153 -0
  92. package/src/resources/docs/leaderboard.md +201 -0
  93. package/src/resources/docs/news.md +114 -0
  94. package/src/resources/docs/overview.md +80 -0
  95. package/src/resources/docs/remote-config.md +149 -0
  96. package/src/resources/docs/user-logs.md +144 -0
  97. package/src/resources/quickstart/godot.md +224 -0
  98. package/src/resources/quickstart/unity.md +317 -0
  99. package/src/resources/quickstart/unreal.md +390 -0
@@ -0,0 +1,280 @@
1
+ # Authentication
2
+
3
+ ## Overview
4
+
5
+ horizOn supports three authentication methods:
6
+
7
+ | Method | Type Value | Description |
8
+ |--------|-----------|-------------|
9
+ | **Anonymous** | `ANONYMOUS` | Token-based, no credentials needed. Ideal for frictionless onboarding. |
10
+ | **Email** | `EMAIL` | Traditional email/password. Supports verification and password reset. |
11
+ | **Google** | `GOOGLE` | OAuth-based. Requires Google authorization code from OAuth flow. |
12
+
13
+ ## Endpoints
14
+
15
+ ### Sign Up
16
+
17
+ **`POST /api/v1/app/user-management/signup`**
18
+
19
+ Creates a new user account.
20
+
21
+ **Request Body:**
22
+
23
+ | Field | Type | Required | Description |
24
+ |-------|------|----------|-------------|
25
+ | `type` | string | Yes | `ANONYMOUS`, `EMAIL`, or `GOOGLE` |
26
+ | `username` | string | No | Display name (1-50 chars) |
27
+ | `email` | string | Email only | User's email address |
28
+ | `password` | string | Email only | Password (4-32 chars) |
29
+ | `anonymousToken` | string | Anonymous only | Unique token (max 32 chars). Auto-generated if omitted. |
30
+ | `googleAuthorizationCode` | string | Google only | OAuth authorization code |
31
+ | `googleRedirectUri` | string | No | Redirect URI used in OAuth flow |
32
+
33
+ **Response (200):**
34
+
35
+ ```json
36
+ {
37
+ "userId": "abc123",
38
+ "username": "Player1",
39
+ "email": "user@example.com",
40
+ "isAnonymous": false,
41
+ "isVerified": true,
42
+ "anonymousToken": null,
43
+ "createdAt": "2025-01-15T10:30:00Z"
44
+ }
45
+ ```
46
+
47
+ **Error Codes:** `401` invalid API key, `409` user already exists.
48
+
49
+ ---
50
+
51
+ ### Sign In
52
+
53
+ **`POST /api/v1/app/user-management/signin`**
54
+
55
+ Authenticates an existing user.
56
+
57
+ **Request Body:**
58
+
59
+ | Field | Type | Required | Description |
60
+ |-------|------|----------|-------------|
61
+ | `type` | string | Yes | `EMAIL`, `ANONYMOUS`, or `GOOGLE` |
62
+ | `email` | string | Email only | User's email |
63
+ | `password` | string | Email only | User's password |
64
+ | `anonymousToken` | string | Anonymous only | The token from signup |
65
+ | `googleAuthorizationCode` | string | Google only | OAuth code |
66
+ | `googleRedirectUri` | string | No | Redirect URI |
67
+
68
+ **Response (200):**
69
+
70
+ ```json
71
+ {
72
+ "userId": "abc123",
73
+ "username": "Player1",
74
+ "email": "user@example.com",
75
+ "accessToken": "session-token-here",
76
+ "authStatus": "AUTHENTICATED",
77
+ "message": null
78
+ }
79
+ ```
80
+
81
+ `authStatus` values: `AUTHENTICATED` (success), `FAILED`, `NOT_VERIFIED`.
82
+
83
+ **Error Codes:** `401` invalid API key, `403` user not verified, `404` user not found.
84
+
85
+ ---
86
+
87
+ ### Check Auth
88
+
89
+ **`POST /api/v1/app/user-management/check-auth`**
90
+
91
+ Validates whether a session token is still active.
92
+
93
+ **Request Body:**
94
+
95
+ ```json
96
+ {
97
+ "userId": "abc123",
98
+ "sessionToken": "session-token-here"
99
+ }
100
+ ```
101
+
102
+ **Response (200):**
103
+
104
+ ```json
105
+ {
106
+ "userId": "abc123",
107
+ "isAuthenticated": true,
108
+ "authStatus": "AUTHENTICATED",
109
+ "message": null
110
+ }
111
+ ```
112
+
113
+ ---
114
+
115
+ ### Change Name
116
+
117
+ **`POST /api/v1/app/user-management/change-name`**
118
+
119
+ Changes the display name of an authenticated user.
120
+
121
+ **Request Body:**
122
+
123
+ ```json
124
+ {
125
+ "userId": "abc123",
126
+ "sessionToken": "session-token-here",
127
+ "newName": "NewDisplayName"
128
+ }
129
+ ```
130
+
131
+ **Response (200):**
132
+
133
+ ```json
134
+ {
135
+ "isAuthenticated": true,
136
+ "authStatus": "AUTHENTICATED"
137
+ }
138
+ ```
139
+
140
+ ---
141
+
142
+ ### Verify Email
143
+
144
+ **`POST /api/v1/app/user-management/verify-email`**
145
+
146
+ Verifies a user's email address using a token sent via email.
147
+
148
+ **Request Body:**
149
+
150
+ ```json
151
+ {
152
+ "token": "verification-token-from-email"
153
+ }
154
+ ```
155
+
156
+ ---
157
+
158
+ ### Forgot Password
159
+
160
+ **`POST /api/v1/app/user-management/forgot-password`**
161
+
162
+ Sends a password reset email to the user.
163
+
164
+ **Request Body:**
165
+
166
+ ```json
167
+ {
168
+ "email": "user@example.com"
169
+ }
170
+ ```
171
+
172
+ ---
173
+
174
+ ### Reset Password
175
+
176
+ **`POST /api/v1/app/user-management/reset-password`**
177
+
178
+ Resets the user's password using a reset token.
179
+
180
+ **Request Body:**
181
+
182
+ ```json
183
+ {
184
+ "token": "reset-token-from-email",
185
+ "newPassword": "newSecurePassword"
186
+ }
187
+ ```
188
+
189
+ ## Code Examples
190
+
191
+ ### Godot (GDScript)
192
+
193
+ ```gdscript
194
+ # Anonymous sign-up
195
+ await Horizon.auth.signUpAnonymous("PlayerName")
196
+
197
+ # Email sign-up
198
+ await Horizon.auth.signUpEmail("user@example.com", "password", "Username")
199
+
200
+ # Email sign-in
201
+ await Horizon.auth.signInEmail("user@example.com", "password")
202
+
203
+ # Restore anonymous session from cached token
204
+ await Horizon.auth.restoreAnonymousSession()
205
+
206
+ # Quick anonymous sign-in (restores or creates new)
207
+ await Horizon.quickSignInAnonymous("Player1")
208
+
209
+ # Check if signed in
210
+ if Horizon.isSignedIn():
211
+ var user = Horizon.getCurrentUser()
212
+ print("Welcome, %s!" % user.displayName)
213
+
214
+ # Change display name
215
+ await Horizon.auth.changeName("NewName")
216
+
217
+ # Sign out (preserves anonymous token by default)
218
+ Horizon.auth.signOut()
219
+
220
+ # Listen for auth events
221
+ Horizon.auth.signin_completed.connect(func(user): print("Signed in: %s" % user.userId))
222
+ Horizon.auth.signin_failed.connect(func(error): print("Error: %s" % error))
223
+ ```
224
+
225
+ ### Unity (C#)
226
+
227
+ ```csharp
228
+ using PM.horizOn.Cloud.Manager;
229
+
230
+ // Anonymous sign-up
231
+ await UserManager.Instance.SignUpAnonymous("PlayerName");
232
+
233
+ // Email sign-up
234
+ await UserManager.Instance.SignUpEmail("user@example.com", "password", "DisplayName");
235
+
236
+ // Email sign-in
237
+ await UserManager.Instance.SignInEmail("user@example.com", "password");
238
+
239
+ // Restore anonymous session
240
+ await UserManager.Instance.RestoreAnonymousSession();
241
+
242
+ // Check if signed in
243
+ if (UserManager.Instance.IsSignedIn)
244
+ {
245
+ var user = UserManager.Instance.CurrentUser;
246
+ Debug.Log($"Welcome, {user.DisplayName}!");
247
+ }
248
+
249
+ // Change name
250
+ await UserManager.Instance.ChangeName("NewName");
251
+
252
+ // Sign out
253
+ UserManager.Instance.SignOut();
254
+ ```
255
+
256
+ ### REST (cURL)
257
+
258
+ ```bash
259
+ # Anonymous signup
260
+ curl -X POST https://horizon.pm/api/v1/app/user-management/signup \
261
+ -H "X-API-Key: YOUR_API_KEY" \
262
+ -H "Content-Type: application/json" \
263
+ -d '{"type": "ANONYMOUS", "username": "Player1"}'
264
+
265
+ # Email signin
266
+ curl -X POST https://horizon.pm/api/v1/app/user-management/signin \
267
+ -H "X-API-Key: YOUR_API_KEY" \
268
+ -H "Content-Type: application/json" \
269
+ -d '{"type": "EMAIL", "email": "user@example.com", "password": "pass123"}'
270
+ ```
271
+
272
+ ## Common Errors
273
+
274
+ | Status | Cause | Solution |
275
+ |--------|-------|----------|
276
+ | 401 | Invalid API key | Check `X-API-Key` header |
277
+ | 403 | User not verified | Complete email verification first |
278
+ | 404 | User not found | User may not exist, try signup |
279
+ | 409 | User already exists | Use signin instead of signup |
280
+ | 429 | Rate limit exceeded | Wait before retrying (10 req/min limit) |
@@ -0,0 +1,180 @@
1
+ # Cloud Save
2
+
3
+ ## Overview
4
+
5
+ Cloud Save allows players to persist their game data across devices and sessions. horizOn supports two modes:
6
+
7
+ - **JSON mode** — Send structured data as a UTF-8 string via `application/json`
8
+ - **Binary mode** — Send raw bytes via `application/octet-stream`
9
+
10
+ ## Tier Limits
11
+
12
+ Data size is limited by the account's pricing tier:
13
+
14
+ | Tier | Max Save Size |
15
+ |------|--------------|
16
+ | FREE | 1 KB |
17
+ | BASIC | 5 KB |
18
+ | PRO | 20 KB |
19
+ | ENTERPRISE | 250 KB |
20
+
21
+ If the save data exceeds the tier limit, the save request will fail with a `403` error.
22
+
23
+ ## Endpoints
24
+
25
+ ### Save Data
26
+
27
+ **`POST /api/v1/app/cloud-save/save`**
28
+
29
+ Saves user data to the cloud. Overwrites any existing save.
30
+
31
+ **Request Body (JSON mode):**
32
+
33
+ | Field | Type | Required | Description |
34
+ |-------|------|----------|-------------|
35
+ | `userId` | string | Yes | The user's ID |
36
+ | `saveData` | string | Yes | Data to save (JSON string) |
37
+
38
+ **Response (200):**
39
+
40
+ ```json
41
+ {
42
+ "success": true,
43
+ "dataSizeBytes": 256
44
+ }
45
+ ```
46
+
47
+ **Binary mode:** `POST /api/v1/app/cloud-save/save?userId={userId}` with `Content-Type: application/octet-stream` and raw bytes in the body.
48
+
49
+ ---
50
+
51
+ ### Load Data
52
+
53
+ **`POST /api/v1/app/cloud-save/load`**
54
+
55
+ Loads the user's saved data from the cloud.
56
+
57
+ **Request Body (JSON mode):**
58
+
59
+ | Field | Type | Required | Description |
60
+ |-------|------|----------|-------------|
61
+ | `userId` | string | Yes | The user's ID |
62
+
63
+ **Response (200):**
64
+
65
+ ```json
66
+ {
67
+ "found": true,
68
+ "saveData": "{\"level\":5,\"coins\":1000}"
69
+ }
70
+ ```
71
+
72
+ If no save data exists:
73
+
74
+ ```json
75
+ {
76
+ "found": false,
77
+ "saveData": null
78
+ }
79
+ ```
80
+
81
+ **Binary mode:** `GET /api/v1/app/cloud-save/load?userId={userId}` with `Accept: application/octet-stream`.
82
+
83
+ ## Code Examples
84
+
85
+ ### Godot (GDScript)
86
+
87
+ ```gdscript
88
+ # Save a Dictionary (recommended approach)
89
+ await Horizon.cloudSave.saveObject({"level": 5, "coins": 1000, "inventory": ["sword", "shield"]})
90
+
91
+ # Load as Dictionary
92
+ var data: Dictionary = await Horizon.cloudSave.loadObject()
93
+ if not data.is_empty():
94
+ var level = data.get("level", 1)
95
+ var coins = data.get("coins", 0)
96
+
97
+ # Save raw JSON string
98
+ await Horizon.cloudSave.saveData('{"level": 5}')
99
+
100
+ # Load raw JSON string
101
+ var json: String = await Horizon.cloudSave.loadData()
102
+
103
+ # Binary data support
104
+ await Horizon.cloudSave.saveBytes(my_packed_byte_array)
105
+ var bytes: PackedByteArray = await Horizon.cloudSave.loadBytes()
106
+
107
+ # Save any Variant using Godot serialization
108
+ await Horizon.cloudSave.saveVariant(my_resource)
109
+ var loaded = await Horizon.cloudSave.loadVariant()
110
+
111
+ # Listen for events
112
+ Horizon.cloudSave.data_saved.connect(func(size): print("Saved %d bytes" % size))
113
+ Horizon.cloudSave.data_loaded.connect(func(data): print("Data loaded"))
114
+ Horizon.cloudSave.data_save_failed.connect(func(error): print("Save failed: %s" % error))
115
+ ```
116
+
117
+ ### Unity (C#)
118
+
119
+ ```csharp
120
+ using PM.horizOn.Cloud.Manager;
121
+
122
+ // Save a typed object as JSON
123
+ [System.Serializable]
124
+ public class GameData
125
+ {
126
+ public int Level;
127
+ public int Coins;
128
+ public string[] Inventory;
129
+ }
130
+
131
+ var saveData = new GameData { Level = 5, Coins = 1000, Inventory = new[] { "sword", "shield" } };
132
+ await CloudSaveManager.Instance.SaveObject(saveData);
133
+
134
+ // Load a typed object
135
+ var loaded = await CloudSaveManager.Instance.LoadObject<GameData>();
136
+ if (loaded != null)
137
+ {
138
+ Debug.Log($"Level: {loaded.Level}, Coins: {loaded.Coins}");
139
+ }
140
+
141
+ // Save/load raw string
142
+ await CloudSaveManager.Instance.Save("{\"level\": 5}");
143
+ string json = await CloudSaveManager.Instance.Load();
144
+
145
+ // Binary data support
146
+ byte[] data = GetBinaryData();
147
+ await CloudSaveManager.Instance.SaveBytes(data);
148
+ byte[] loadedBytes = await CloudSaveManager.Instance.LoadBytes();
149
+ ```
150
+
151
+ ### REST (cURL)
152
+
153
+ ```bash
154
+ # Save data (JSON mode)
155
+ curl -X POST https://horizon.pm/api/v1/app/cloud-save/save \
156
+ -H "X-API-Key: YOUR_API_KEY" \
157
+ -H "Content-Type: application/json" \
158
+ -d '{"userId": "user123", "saveData": "{\"level\":5,\"coins\":1000}"}'
159
+
160
+ # Load data (JSON mode)
161
+ curl -X POST https://horizon.pm/api/v1/app/cloud-save/load \
162
+ -H "X-API-Key: YOUR_API_KEY" \
163
+ -H "Content-Type: application/json" \
164
+ -d '{"userId": "user123"}'
165
+ ```
166
+
167
+ ## Best Practices
168
+
169
+ - **Save on meaningful events** — Save when a level is completed, not on every frame or action.
170
+ - **Compress large saves** — If nearing the tier limit, consider compressing JSON data.
171
+ - **Handle "not found" gracefully** — On first load, `found` will be `false`. Initialize defaults.
172
+ - **Use typed objects** — Both SDKs support saving/loading typed objects directly.
173
+
174
+ ## Common Errors
175
+
176
+ | Status | Cause | Solution |
177
+ |--------|-------|----------|
178
+ | 401 | Invalid API key | Check `X-API-Key` header |
179
+ | 403 | Save exceeds tier limit | Reduce data size or upgrade tier |
180
+ | 429 | Rate limit exceeded | Batch saves, save less frequently |
@@ -0,0 +1,126 @@
1
+ # User Feedback
2
+
3
+ ## Overview
4
+
5
+ The User Feedback feature lets players submit bug reports, feature requests, and general feedback directly from your app. All submissions are visible in the horizOn Dashboard. Optional device information can be attached to help with debugging.
6
+
7
+ ## Endpoints
8
+
9
+ ### Submit Feedback
10
+
11
+ **`POST /api/v1/app/user-feedback/submit`**
12
+
13
+ Submits a feedback entry.
14
+
15
+ **Request Body:**
16
+
17
+ | Field | Type | Required | Description |
18
+ |-------|------|----------|-------------|
19
+ | `title` | string | Yes | Feedback title (1-100 characters) |
20
+ | `message` | string | Yes | Feedback message (1-2048 characters) |
21
+ | `userId` | string | Yes | The user's ID |
22
+ | `email` | string | No | Contact email for follow-up |
23
+ | `category` | string | No | Category: `BUG`, `FEATURE`, or `GENERAL` |
24
+ | `deviceInfo` | string | No | Device information (max 500 characters) |
25
+
26
+ **Response (200):**
27
+
28
+ ```
29
+ "ok"
30
+ ```
31
+
32
+ ## Code Examples
33
+
34
+ ### Godot (GDScript)
35
+
36
+ ```gdscript
37
+ # Submit general feedback
38
+ await Horizon.feedback.submit("Great Game!", "I love the new features.", "GENERAL")
39
+
40
+ # Submit bug report with auto device info
41
+ await Horizon.feedback.submitBugReport("Crash on Level 5", "Game crashes when opening inventory")
42
+
43
+ # Submit feature request
44
+ await Horizon.feedback.submitFeatureRequest("Dark Mode", "Please add a dark mode option")
45
+
46
+ # Full submit with all options
47
+ await Horizon.feedback.submit(
48
+ "Bug Title",
49
+ "Detailed description of the bug...",
50
+ "BUG",
51
+ "user@example.com", # optional email
52
+ true # include device info
53
+ )
54
+
55
+ # Listen for events
56
+ Horizon.feedback.feedback_submitted.connect(func(): print("Feedback sent!"))
57
+ Horizon.feedback.feedback_submit_failed.connect(func(error): print("Failed: %s" % error))
58
+ ```
59
+
60
+ **Automatic device info includes:** OS, model, Godot version, screen resolution, renderer, and memory usage.
61
+
62
+ ### Unity (C#)
63
+
64
+ ```csharp
65
+ using PM.horizOn.Cloud.Manager;
66
+
67
+ // Bug report with auto device info
68
+ await FeedbackManager.Instance.ReportBug(
69
+ title: "Crash on level 5",
70
+ message: "Game crashes when opening inventory"
71
+ );
72
+
73
+ // Feature request
74
+ await FeedbackManager.Instance.RequestFeature(
75
+ title: "Dark mode",
76
+ message: "Please add dark mode option"
77
+ );
78
+
79
+ // General feedback
80
+ await FeedbackManager.Instance.SendGeneral(
81
+ title: "Great game!",
82
+ message: "Really enjoying the new update"
83
+ );
84
+
85
+ // Full submit with all options
86
+ await FeedbackManager.Instance.Submit(
87
+ title: "Bug Title",
88
+ category: "BUG",
89
+ message: "Detailed description...",
90
+ email: "user@example.com",
91
+ includeDeviceInfo: true
92
+ );
93
+ ```
94
+
95
+ **Automatic device info includes:** Unity version, OS, device model, and graphics device.
96
+
97
+ ### REST (cURL)
98
+
99
+ ```bash
100
+ curl -X POST https://horizon.pm/api/v1/app/user-feedback/submit \
101
+ -H "X-API-Key: YOUR_API_KEY" \
102
+ -H "Content-Type: application/json" \
103
+ -d '{
104
+ "title": "Bug Report",
105
+ "message": "Game crashes on level 5",
106
+ "userId": "user123",
107
+ "email": "user@example.com",
108
+ "category": "BUG",
109
+ "deviceInfo": "Windows 11 | RTX 3080 | 16GB RAM"
110
+ }'
111
+ ```
112
+
113
+ ## Best Practices
114
+
115
+ - **Include device info for bug reports** — Both SDKs auto-collect device info; enable it for bug reports.
116
+ - **Use categories** — Categorize feedback as `BUG`, `FEATURE`, or `GENERAL` for easier triage in the Dashboard.
117
+ - **Add contact email** — Include the user's email if they want to receive follow-up responses.
118
+ - **Rate limit awareness** — Feedback submission counts toward the 10 req/min limit.
119
+
120
+ ## Common Errors
121
+
122
+ | Status | Cause | Solution |
123
+ |--------|-------|----------|
124
+ | 400 | Missing required fields | Ensure `title`, `message`, and `userId` are provided |
125
+ | 401 | Invalid API key | Check `X-API-Key` header |
126
+ | 429 | Rate limit exceeded | Limit feedback submissions per session |