Rate Limiting
The Bifrost API enforces rate limits to ensure fair usage and protect service stability. Understanding and respecting these limits is essential for building reliable integrations.
How It Works
When you exceed a rate limit, the API returns an HTTP 429 response
with a RATE_LIMITED error code.
The response includes a retryAfter value
(in seconds) indicating how long to wait before retrying. Your application must read this value and
delay the next request accordingly.
Rate limits are tracked per endpoint and scoped to either your guild (shared across all servers) or a specific server (independent per server). See the HTTP Status Codes page for the full 429 response format.
Overall Request Budget
In addition to per-endpoint limits, each guild has an overall budget of 1,000 API requests per hour for each server (by game type) in your guild.
| Scenario | Hourly Budget |
|---|---|
| 1 HLL server | 1,000 requests/hour |
| 2 HLL servers | 2,000 requests/hour |
| 1 HLL server + 1 HLLV server | 2,000 requests/hour |
This budget applies to all queries and mutations combined. Plan your polling intervals to stay within this envelope.
Guild Scope
The rate limit is shared across all servers in your guild. One request from any server counts toward the same limit. Used for endpoints that return guild-wide data or configuration.
Server Scope
The rate limit is tracked independently per server. You can make the maximum number of requests to each server without affecting the others. Used for endpoints that return per-server data.
Per-Endpoint Limits
| Endpoint | Scope | Rate Limit |
|---|---|---|
OAuth Authentication | Guild | 1 request per 30 minutes |
testQuery | Guild | 1 per 60 seconds |
publicAnnouncements | N/A | No limit |
guildGetGameState | Server | 1 per 30 seconds |
guildGetPlayers | Server | 1 per 30 seconds |
guildGetVips | Server | 150 per 5 minutes |
guildAddVip | Server | 150 per 5 minutes |
guildRemoveVip | Server | 150 per 5 minutes |
guildGetPlayerVip | Guild | 1 per 60 seconds per playerId |
guildGetAllMaps | Guild | 1 per 4 hours |
guildGetMatches | Server | 1 per 30 minutes |
guildGetMatchDetail | Server | 1 per 5 minutes per matchId |
guildChangeMap | Server | 1 per 2 minutes |
guildGetServerRotation | Server | 1 per 60 seconds |
guildSetServerRotation | Server | 1 per 60 seconds |
guildGetLogs | Server | 1 per 15 seconds |
guildGetRawLogs | Server | 1 per 15 seconds |
guildGetExemptions | Server | 1 per 5 minutes |
guildSearchPlayer | Server | 1 per 15 seconds |
guildGetPlayerHistory | Server | 1 per 30 minutes per playerId |
guildGetBannedChat | Server | 1 per 5 minutes |
guildGetModActions | Server | 1 per 5 minutes |
guildGetFlags | Guild | 1 per 5 minutes |
guildGetFlaggedPlayers | Guild | 1 per 5 minutes |
guildMessagePlayer | Server | 150 per 5 minutes |
guildTeamSwap | Server | 150 per 5 minutes |
guildWarnPlayer | Server | 150 per 5 minutes |
guildPunishPlayer | Server | 150 per 5 minutes |
guildKickPlayer | Server | 150 per 5 minutes |
guildTempBanPlayer | Server | 150 per 5 minutes |
guildPermBanPlayer | Server | 150 per 5 minutes |
guildFlagPlayer | Server | 150 per 5 minutes |
guildRemoveFlagPlayer | Server | 150 per 5 minutes |
guildAddMember | Guild | 150 per 2 minutes |
guildRemoveMember | Guild | 150 per 2 minutes |
guildGetSupporterConfig | Guild | 1 per 5 minutes |
guildAddSupporter | Guild | 150 per 5 minutes |
guildLapseSupporter | Guild | 150 per 5 minutes |
guildRemoveSupporter | Guild | 150 per 5 minutes |
guildGetAPIStatus | Guild | 1 per 5 minutes |
OAuth Token Requests
The OAuth token endpoint (https://api.dev.bifrostgaming.com/v1/oauth/token)
is limited to 1 request per 30 minutes per guild. Access tokens are valid for
1 hour. Your application should cache the token and only request a new one when the current token is
close to expiry. Do not request a new token on every API call.
Handling Rate Limit Responses
When rate limited, the API returns HTTP 429 with the following response body.
The retryAfter field
tells you exactly how many seconds to wait before retrying.
{
"errors": [
{
"message": "Rate limit exceeded. Please wait before making another request.",
"extensions": {
"code": "RATE_LIMITED",
"retryAfter": 15
}
}
]
} Best Practices
- Cache responses locally and only re-fetch when your cache expires or data is expected to change.
- Read the
retryAftervalue from 429 responses and wait that exact duration before retrying. - Use exponential backoff if you receive multiple consecutive 429 responses.
- Request OAuth tokens only when the current token is expired or about to expire, not on every API call.
- For server-scoped endpoints, stagger requests across your servers rather than sending them all at once.
Code Examples
The following examples demonstrate how to properly handle rate limiting in your application.
Both examples read the retryAfter value
and wait before retrying.
Python
import time
import requests
API_URL = "https://api.dev.bifrostgaming.com/v1/graphql"
def make_request(query, variables=None, access_token=None, max_retries=3):
"""Make an API request with automatic rate limit handling."""
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {access_token}"
}
payload = {"query": query}
if variables:
payload["variables"] = variables
for attempt in range(max_retries):
response = requests.post(API_URL, json=payload, headers=headers)
if response.status_code == 200:
return response.json()
if response.status_code == 429:
# Read the server-provided retry delay
data = response.json()
retry_after = 30 # fallback
for error in data.get("errors", []):
ext = error.get("extensions", {})
if "retryAfter" in ext:
retry_after = ext["retryAfter"]
break
print(f"Rate limited. Waiting {retry_after}s before retry "
f"(attempt {attempt + 1}/{max_retries})")
time.sleep(retry_after)
continue
# Other error — do not retry
response.raise_for_status()
raise Exception("Max retries exceeded due to rate limiting")
# Usage
result = make_request(
query='{ guildGetGameState(serverId: "abc", gameType: "HLL") { data } }',
access_token="your_token_here"
)
print(result) .NET (C#)
using System.Text.Json;
public class BifrostClient
{
private readonly HttpClient _client;
private const string ApiUrl = "https://api.dev.bifrostgaming.com/v1/graphql";
private const int MaxRetries = 3;
public BifrostClient(string accessToken)
{
_client = new HttpClient();
_client.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}");
}
public async Task<JsonDocument> QueryAsync(string query, object? variables = null)
{
var payload = variables != null
? new { query, variables }
: (object)new { query };
for (int attempt = 0; attempt < MaxRetries; attempt++)
{
var response = await _client.PostAsJsonAsync(ApiUrl, payload);
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
return JsonDocument.Parse(json);
}
if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
{
// Parse the retryAfter value from the GraphQL error response
var errorJson = await response.Content.ReadAsStringAsync();
var errorDoc = JsonDocument.Parse(errorJson);
var retryAfter = 30; // fallback
if (errorDoc.RootElement.TryGetProperty("errors", out var errors))
{
foreach (var error in errors.EnumerateArray())
{
if (error.TryGetProperty("extensions", out var ext) &&
ext.TryGetProperty("retryAfter", out var ra))
{
retryAfter = ra.GetInt32();
break;
}
}
}
Console.WriteLine(
$"Rate limited. Waiting {retryAfter}s " +
$"(attempt {attempt + 1}/{MaxRetries})");
await Task.Delay(TimeSpan.FromSeconds(retryAfter));
continue;
}
// Other error — do not retry
response.EnsureSuccessStatusCode();
}
throw new Exception("Max retries exceeded due to rate limiting");
}
}
// Usage
var client = new BifrostClient("your_token_here");
var result = await client.QueryAsync(
@"{ guildGetGameState(serverId: ""abc"", gameType: ""HLL"") { data } }"
);
Console.WriteLine(result.RootElement);