REST API Integration v1 HTTP
Overview
Every Keme SDK (JavaScript, Unity, Android, iOS) wraps the same player-portal REST API. If you can't use a native SDK — or you want full control — you can call the API directly. This is the exact contract the SDKs mirror; the JavaScript SDK is the reference implementation.
The API is player-facing: each call is authenticated with a player bearer token obtained from login. There is no separate registration — the first login auto-provisions the player. All endpoints return JSON wrapped in a consistent envelope.
- Player login and profile (auto-provisioning on first contact)
- Full ticket lifecycle — create, list, get, reply, rate
- Knowledge base — search, categories, featured, article-by-slug
- Consistent { success, data } response envelope
- Typed error responses with standard HTTP status codes
Base URL
All requests are made to the following base URL. Append the endpoint path to it for every request.
1https://api.kemegames.com/api/v1
Response Envelope
Every successful response is wrapped in a { success, data } envelope — the payload you want is in data. List endpoints nest a paginated object inside data. The SDKs unwrap this for you and normalise lists to a plain array.
1{2 "success": true,3 "data": {4 "id": "tkt_xxxxxxxxxxxxxxxxxx",5 "subject": "Payment did not go through",6 "status": "open"7 }8}
1{2 "success": true,3 "data": {4 "data": [ /* Ticket[] */ ],5 "total": 42,6 "page": 1,7 "limit": 208 }9}
Authentication
Portal endpoints use a player bearer token — a JWT with a 7-day expiry returned by POST /portal/auth/login. Send it in the Authorization header using the Bearer scheme on every authenticated request. All requests use Content-Type: application/json.
1curl https://api.kemegames.com/api/v1/portal/auth/me \2 -H "Authorization: Bearer <player-token>" \3 -H "Content-Type: application/json"
Concepts: gameId vs userId
- gameId identifies your game/app (e.g. game-marble-sort). It is set once at SDK init and passed when creating a ticket.
- userId identifies a player uniquely within your game and maps to the portal gameUid. The first login auto-provisions the player — no separate registration.
- A banned player's login is rejected with 403.
Errors
Non-2xx responses return { message, statusCode }, where message is a string or an array of validation strings. The SDKs surface this as a typed KemeError with message, status, and the raw body.
1{2 "message": [3 "subject must be longer than or equal to 5 characters"4 ],5 "statusCode": 4006}
The API uses standard HTTP status codes:
- 200 OK — Request succeeded. The result is in data.
- 201 Created — Resource created (e.g. a new ticket). The new resource is in data.
- 400 Bad Request — The body or query parameters failed validation.
- 401 Unauthorized — The Authorization header is missing or the token is invalid or expired.
- 403 Forbidden — The player is not permitted to perform the action (e.g. a banned player).
- 404 Not Found — The requested resource does not exist.
- 500 Internal Server Error — An unexpected server-side error occurred.
Login
Authenticate a player. Send your player's id as gameUid; the first login for a given gameUid auto-provisions the player. username and email are optional and used only when the player is first provisioned. Returns a token and the player object.
1curl -X POST https://api.kemegames.com/api/v1/portal/auth/login \2 -H "Content-Type: application/json" \3 -d '{4 "gameUid": "player-12345",5 "username": "Ari",6 "email": "ari@example.com"7 }'
1{2 "success": true,3 "data": {4 "token": "" ,5 "player": {6 "id": "plr_xxxxxxxxxxxx",7 "username": "Ari",8 "gameUid": "player-12345",9 "vipTier": "bronze"10 }11 }12}
Current Player
Fetch the authenticated player's profile.
1curl https://api.kemegames.com/api/v1/portal/auth/me \2 -H "Authorization: Bearer <player-token>"
Creating a Ticket
Submit a new ticket for the authenticated player. subject (5–200 chars), description (10–5000 chars), and gameId are required. category and priority are optional. Returns 201 Created with the new ticket.
1curl -X POST https://api.kemegames.com/api/v1/portal/tickets \2 -H "Authorization: Bearer <player-token>" \3 -H "Content-Type: application/json" \4 -d '{5 "subject": "Payment did not go through",6 "description": "I was charged but did not receive my gems.",7 "gameId": "game-marble-sort",8 "category": "billing",9 "priority": "P2"10 }'
Valid categories: bug | billing | account | gameplay | other. Valid priorities: P1 (critical) | P2 (high) | P3 (normal) | P4 (low).
1{2 "success": true,3 "data": {4 "id": "tkt_xxxxxxxxxxxxxxxxxx",5 "subject": "Payment did not go through",6 "status": "open",7 "priority": "P2",8 "category": "billing",9 "createdAt": "2026-07-18T10: 23: 45Z",10 "updatedAt": "2026-07-18T10: 23: 45Z"11 }12}
Listing Tickets
Retrieve the authenticated player's tickets. Optional query parameters: page, limit, and status. The paginated list is nested inside data.
1curl "https://api.kemegames.com/api/v1/portal/tickets?page=1&limit=20&status=open" \2 -H "Authorization: Bearer <player-token>"
Getting a Ticket
Fetch a single ticket by id, including its messages[] thread.
1curl https://api.kemegames.com/api/v1/portal/tickets/tkt_xxxx \2 -H "Authorization: Bearer <player-token>"
Replying to a Ticket
Add a message to a ticket. The body field (1–5000 chars) is required. Returns the new message.
1curl -X POST https://api.kemegames.com/api/v1/portal/tickets/tkt_xxxx/messages \2 -H "Authorization: Bearer <player-token>" \3 -H "Content-Type: application/json" \4 -d '{ "body": "Any update on my refund?" }'
Rating a Ticket
Rate a resolved ticket. score (1–5) is required; comment is optional.
1curl -X POST https://api.kemegames.com/api/v1/portal/tickets/tkt_xxxx/rate \2 -H "Authorization: Bearer <player-token>" \3 -H "Content-Type: application/json" \4 -d '{ "score": 5, "comment": "Fast and friendly, thanks!" }'
Knowledge Base
Surface self-serve help before a player files a ticket. The knowledge base endpoints are also player-authenticated.
- GET /portal/kb/search?q=<query> — search articles
- GET /portal/kb/categories — list categories
- GET /portal/kb/featured — featured articles
- GET /portal/kb/articles/:slug — a single article by slug
1curl "https://api.kemegames.com/api/v1/portal/kb/search?q=restore%20purchase" \2 -H "Authorization: Bearer <player-token>"
Language Examples
The following examples show the create-ticket call from the Creating a Ticket section in JavaScript/Node.js and Python. In production, prefer the JavaScript SDK, which handles the envelope, token persistence, and errors for you.
JavaScript / Node.js
1const response = await fetch('https://api.kemegames.com/api/v1/portal/tickets', {2 method: 'POST',3 headers: {4 'Authorization': `Bearer ${playerToken}`,5 'Content-Type': 'application/json',6 },7 body: JSON.stringify({8 subject: 'Payment did not go through',9 description: 'I was charged but did not receive my gems.',10 gameId: 'game-marble-sort',11 category: 'billing',12 priority: 'P2',13 }),14});1516if (!response.ok) {17 const err = await response.json();18 const message = Array.isArray(err.message) ? err.message.join(', ') : err.message;19 throw new Error(`Keme API error ${response.status}: ${message}`);20}2122const { data: ticket } = await response.json(); // unwrap the envelope23console.log('Created ticket:', ticket.id);
Python
1import requests23response = requests.post(4 "https://api.kemegames.com/api/v1/portal/tickets",5 headers={6 "Authorization": f"Bearer {player_token}",7 "Content-Type": "application/json",8 },9 json={10 "subject": "Payment did not go through",11 "description": "I was charged but did not receive my gems.",12 "gameId": "game-marble-sort",13 "category": "billing",14 "priority": "P2",15 },16)1718response.raise_for_status()19ticket = response.json()["data"] # unwrap the envelope20print("Created ticket:", ticket["id"])