Core concepts
Projects, keys, player sessions and how every SDK reports results and errors.
Projects and keys#
Each game is a project. Its keys identify the project; they do not identify a player.
| Credential | Where it goes |
|---|---|
| Base URL | The address of this install. The API lives under /v1. |
| Project ID | Every request, as X-Project-ID. Public. |
Client key pk_live_… | Game builds and web pages, as X-Client-Key. Public: it can only act for signed-in players. |
Secret key sk_live_… | Your own servers only, as X-Secret-Key. Shown once and stored hashed; unlocks server-only endpoints. |
sk_live_…) in a game build or web page. Anything shipped to players can be read.Players and sessions#
Player calls need an access token. Signing in returns one (valid for one hour) together with a refresh token (valid for 30 days).
- The SDKs store the session on the device and refresh it before it expires, so players stay signed in across launches.
- Refresh tokens rotate on every use. Reusing an old one signs that session out everywhere (theft detection).
- Every player starts as a guest and can later link an email or Google account; the guest's progress moves along. See Players.
Results instead of exceptions#
SDK calls never throw on network or server errors. They return a result with ok, a value, and an error with a stable code, a message and the request ID.
var top = await Majacloud.Leaderboards.GetTopAsync("coins", 10);
if (top.Ok)
{
foreach (var entry in top.Value.Entries)
Debug.Log($"#{entry.Rank} {entry.PlayerName} {entry.Score}");
}
else if (top.Error.IsRetryable)
{
// Network, timeout, 5xx or rate limit: try again later
Debug.Log("Retry in " + top.Error.RetryAfterSeconds + " s");
}
else
{
Debug.LogWarning(top.Error.Code + ": " + top.Error.Message + " (" + top.Error.RequestId + ")");
}var top := await Majacloud.leaderboards.get_top("coins", 10)
if top.ok:
for entry in top.value.entries:
print("#%d %s %d" % [entry.rank, entry.player_name, entry.score])
elif top.is_retryable():
# Network, timeout, 5xx or rate limit: try again later
print("Retry in ", top.retry_after, " s")
else:
push_warning("%s: %s (%s)" % [top.error, top.message, top.request_id])const top = await mc.leaderboards.getTop('coins', { limit: 10 });
if (top.ok) {
for (const entry of top.value.entries) {
console.log(`#${entry.rank} ${entry.player_name} ${entry.score}`);
}
} else if (top.error.retryable) {
// Network, timeout, 5xx or rate limit: try again later
console.log('Retry in', top.error.retryAfter, 's');
} else {
console.warn(`${top.error.code}: ${top.error.message} (${top.error.requestId})`);
}// Success
{ "ok": true, "entries": [ … ], "total": 42 }
// Error (HTTP 4xx/5xx)
{
"ok": false,
"error": {
"code": "rate_limited",
"message": "Project rate limit reached. Try again in a minute.",
"request_id": "6f1c2a9e-…",
"details": { "limit": 120, "window": "minute" }
}
}Retries and rate limits#
The SDKs retry safe requests on network errors and 5xx responses with exponential backoff, and wait out 429 responses using Retry-After.
- Each project may send up to 120 requests per minute; above that the API answers
rate_limited. - Each account has a daily request quota that depends on the plan (see Limits); above it the API answers
quota_exceededuntil 00:00 UTC. - Responses carry
X-RateLimit-Limit,X-RateLimit-Remainingand, near the quota,X-RateLimit-Daily-Remaining.
Request IDs and logs#
Every response has an X-Request-ID header, and every error includes request_id. Search for it on the Logs page of your project to see the endpoint, status, latency and player.