Bifrost Bifrost

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 retryAfter value 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

rate_limit_handler.py
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#)

BifrostClient.cs
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);

In partnership with

Brilliant game servers for communities large and small.

Get yours today!

Shrapnelworks Logo

A Shrapnelworks product