Bifrost Bifrost

Partner Integration Guide

Welcome to the Bifrost Partner API. This guide will walk you through everything you need to integrate your platform with Bifrost — from setting up encryption to creating guilds and managing game server billing.

Before you begin

To use the Partner API, you need a Partner Account and an Encryption Key. These are provided by the Bifrost team during onboarding. If you don't have these yet, please contact your Bifrost account manager.

How the Partner API Works

Unlike the standard Bifrost API (which uses OAuth 2.0 tokens), the Partner API uses AES-256-GCM encryption for authentication. Instead of sending a bearer token, you encrypt your entire request payload with your unique encryption key. If the server can decrypt it, that proves you are who you say you are.

1

Build your payload

Create a JSON object with the data for your request (e.g. guild details, server action). Include a timestamp and nonce for security.

2

Encrypt it

Encrypt the JSON string using AES-256-GCM with your partner key. Base64-encode the result. This becomes the encryptedData field.

3

Send the GraphQL request

POST the GraphQL mutation to the API with your partnerId and the encrypted payload. No bearer token needed.

What You'll Need

Item Description Example
partnerId Your unique partner identifier your-partner-id
encryptionKey Base64-encoded 256-bit AES key YOUR_BASE64_ENCRYPTION_KEY
API URL The Bifrost GraphQL endpoint https://api.bifrostgaming.com/v1/graphql

Encryption Guide

Every Partner API request requires you to encrypt your JSON payload using AES-256-GCM. This is an industry-standard authenticated encryption algorithm — it both encrypts your data and verifies it hasn't been tampered with.

Important: Your encryption key is a Base64-encoded string. Before using it, you must Base64-decode it to get the raw 32 bytes. Do not use the Base64 string directly as the key — that won't work.

Encrypted Data Format

After encrypting, you combine three pieces into a single byte array, then Base64-encode the whole thing:

[12-byte IV][ciphertext][16-byte auth tag]  →  Base64 encode  →  encryptedData
Component Size What it is
IV 12 bytes Initialisation Vector — randomly generated for every request. Never reuse an IV with the same key.
Ciphertext Variable Your JSON payload, encrypted. Same length as the original plaintext.
Auth Tag 16 bytes Authentication tag — proves the data hasn't been modified. GCM generates this automatically.

Encryption Code Examples

Here's how to encrypt a payload in each language. You can copy this directly into your project:

# Encryption must be done in code before the cURL call.
# Here's a complete Node.js script that encrypts and sends:

node -e "
const crypto = require('crypto');
const https = require('https');

// Your credentials
const PARTNER_ID = 'your-partner-id';
const KEY_BASE64 = 'YOUR_BASE64_ENCRYPTION_KEY';

// Build the payload
const payload = {
  action: 'CREATE',
  gameServerId: 'your-server-uuid',
  mode: 'LIVE',
  timestamp: Date.now(),
  nonce: crypto.randomBytes(16).toString('hex')
};

// Encrypt
const key = Buffer.from(KEY_BASE64, 'base64');
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const ciphertext = Buffer.concat([
  cipher.update(JSON.stringify(payload), 'utf8'),
  cipher.final()
]);
const tag = cipher.getAuthTag();
const encrypted = Buffer.concat([iv, ciphertext, tag]).toString('base64');

console.log('Encrypted payload:', encrypted);
console.log('Length:', encrypted.length, 'characters');
"

Required Security Fields

Every encrypted payload — regardless of which endpoint you're calling — must include these two fields to prevent replay attacks:

Field Type Description
timestamp Number Current time as Unix milliseconds (e.g. 1705849200000). Must be within 5 minutes of the server's clock. If your request is rejected for an expired timestamp, check that your system clock is accurate.
nonce String A unique string, at least 16 characters long. Use a UUID or random hex string. Each request must have a different nonce — never reuse one.

Example security fields

{
  "timestamp": 1705849200000,
  "nonce": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
  ...your other fields here...
}

Available Partner Endpoints

The Partner API provides two main endpoints. Click on each to see full documentation with request/response schemas and code examples.

Server Billing Status Lifecycle

Every game server has a billing status. The Partner API lets you transition servers between these statuses. Here's what each status means and which transitions are allowed:

ACTIVE

The server is live and will be billed. This is the normal operating state.

Can transition to: ACTIVEFREE, INACTIVE, NOPAYMENT, CANCELLED, CANCELLEDREFUNDED

ACTIVEFREE

The server is live but not billed. Used for free-tier, promotional, or internal servers.

Can transition to: ACTIVE, INACTIVE, NOPAYMENT, CANCELLED, CANCELLEDREFUNDED

INACTIVE

The server has had no match data for 5+ rolling days. It's still billable but flagged as inactive.

Can transition to: ACTIVE, ACTIVEFREE, NOPAYMENT, CANCELLED, CANCELLEDREFUNDED

NOPAYMENT

The server has payment issues and is not billed.

Can transition to: ACTIVE, ACTIVEFREE, CANCELLED

CANCELLED

The server is permanently decommissioned. This is a terminal state — it cannot be changed.

No further transitions allowed.

CANCELLEDREFUNDED

The server was cancelled within the refund grace period (default: 72 hours from creation). Terminal state.

No further transitions allowed. Only available if the server was created less than 72 hours ago.

LIVE vs TEST mode: When you create or activate a server, you specify a mode of either LIVE or TEST. TEST servers are completely excluded from billing calculations — use this for development and testing. LIVE servers are included in billing.

Guild Creation Payload Reference

When calling partnerCreateGuild, the JSON you encrypt must follow this exact structure:

// This is the JSON you encrypt — NOT the GraphQL query
{
  "user": {
    "email": "[email protected]",
    "username": "CommunityOwner",
    "firstName": "John",
    "lastName": "Doe",
    "discordId": "123456789012345678"
  },
  "guild": {
    "name": "My Awesome Community",
    "abbreviation": "MAC",
    "discordUrl": "https://discord.gg/example",
    "description": "A friendly gaming community",
    "websiteUrl": "https://example.com",
    "countries": ["GB", "US"],
    "is18Plus": true,
    "isRecruiting": true,
    "isCompetitive": false,
    "isPcPlayers": true,
    "isConsolePlayers": false
  },
  "metadata": {
    "ownerId": "[email protected]"
  },
  "options": {
    "sendWelcomeEmail": false
  },
  "timestamp": 1705849200000,
  "nonce": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
}

User Fields

Field Required Description
user.email Required Valid email address. Must be unique across all Bifrost users.
user.username Required Display name, minimum 2 characters.
user.firstName Optional Owner's first name.
user.lastName Optional Owner's last name.
user.discordId Optional Discord user ID (snowflake). If provided, the Discord account is automatically linked to their Bifrost account.

Guild Fields

Field Required Description
guild.name Required Community name, minimum 2 characters.
guild.abbreviation Required Short code (max 10 chars). Must be globally unique (e.g. "MAC", "504D").
guild.discordUrl Optional Discord invite URL (e.g. https://discord.gg/example).
guild.description Optional A short description of the community.
guild.websiteUrl Optional Community website URL.
guild.countries Required Array of ISO country codes where the community operates. Must have at least one entry.
guild.is18Plus Required Whether this is an 18+ community.
guild.isRecruiting Required Whether the community is currently accepting new members.
guild.isCompetitive Required Whether this is a competitive/tournament community.
guild.isPcPlayers Required Whether the community includes PC players.
guild.isConsolePlayers Required Whether the community includes console players.

Options & Metadata

Field Required Description
options.sendWelcomeEmail Optional If true, the owner receives a welcome email with a password reset link (valid 12 hours) and no temporaryPassword is returned. If false (default), a temporary password is generated and returned in the response for you to share securely.
metadata.ownerId Required Must be set to the guild owner's email address (must match user.email). This is your persistent identifier for targeting this guild in all future server actions (CREATE, CHANGE_STATUS, DELETE, CHANGE_EMAIL). One ownerId can only be associated with one guild per partner. The owner's email is managed by you — the user cannot change it themselves in the Bifrost control panel.
metadata.* Optional You may include additional fields in metadata for your own tracking purposes (e.g. internal customer ID). These are stored alongside the guild record but not used by Bifrost.

Server Action Payload Reference

When calling partnerServerAction, the JSON you encrypt depends on the action you want to perform:

CREATE — Register a new game server

This is the main action for onboarding servers. It creates a fully configured game server on the Bifrost platform, links it to your partner guild, and activates billing. You must have already created a guild via partnerCreateGuild before creating servers.

{
  "action": "CREATE",
  "ownerId": "[email protected]",
  "serverGameType": "HLL",
  "serverName": "My Community Server #1",
  "serverIP": "203.0.113.50",
  "serverQueryPort": 27015,
  "serverRCONPort": 27020,
  "serverRCONPassword": "MySecureRconPass123!",
  "serverCountry": "US",
  "serverTimezone": "America/New_York",
  "serverPlatform": "PC",
  "mode": "LIVE",
  "timestamp": 1705849200000,
  "nonce": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
}

Important: The response includes a serverId. Store this value — you will need it for all future CHANGE_STATUS and DELETE actions on this server.

CHANGE_STATUS — Change a server's billing state

{
  "action": "CHANGE_STATUS",
  "ownerId": "[email protected]",
  "gameServerId": "server-uuid-from-create-response",
  "status": "NOPAYMENT",
  "reason": "Payment method declined",
  "timestamp": 1705849200000,
  "nonce": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
}

DELETE — Cancel a server permanently

Permanently cancels a server. The server record is not deleted from the database — it is set to CANCELLED status and deactivated. This is a terminal state and cannot be reversed.

{
  "action": "DELETE",
  "ownerId": "[email protected]",
  "gameServerId": "server-uuid-from-create-response",
  "reason": "Customer cancelled subscription",
  "timestamp": 1705849200000,
  "nonce": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
}

CHANGE_EMAIL — Update a guild owner's email

Updates the guild owner's email address across the identity provider (Keycloak), the database, and the guild metadata — all atomically. If either the identity provider or database update fails, everything is rolled back. The user's login email changes immediately. Use this when a customer changes their email address.

{
  "action": "CHANGE_EMAIL",
  "ownerId": "[email protected]",
  "newEmail": "[email protected]",
  "timestamp": 1705849200000,
  "nonce": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
}

Important: After a successful CHANGE_EMAIL, use the new email as the ownerId in all future requests. The old email will no longer work.

CREATE Action Fields

Field Required Description
action Required Must be "CREATE"
ownerId Required The guild owner's email address (as provided in metadata.ownerId during guild creation). Identifies which guild to link the server to.
serverGameType Required The game type. Currently supported: "HLL" (Hell Let Loose)
serverName Required Display name for the server (e.g. "My Community Server #1"). A unique server key is auto-generated from this.
serverIP Required The server's IP address (e.g. "203.0.113.50"). Must be reachable by the Bifrost platform.
serverQueryPort Required The server's Steam Query port (integer, e.g. 27015). Used for server status polling.
serverRCONPort Required The server's RCON port (integer, e.g. 27020). Used for remote console commands.
serverRCONPassword Required The server's RCON password. Stored encrypted at rest.
serverCountry Optional ISO country code where the server is hosted (e.g. "US", "DE", "GB"). Defaults to "Unknown".
serverTimezone Optional IANA timezone (e.g. "America/New_York", "Europe/London"). Defaults to "UTC".
serverPlatform Optional "PC" (default) or "Console".
mode Optional "LIVE" (default, billable) or "TEST" (excluded from billing). Use TEST for development/staging servers.

CHANGE_STATUS & DELETE Action Fields

Field Required Description
action Required "CHANGE_STATUS" or "DELETE"
ownerId Required The guild owner's email address. The server must belong to a guild with this ownerId. Prevents cross-guild operations.
gameServerId Required The UUID returned by a previous CREATE action. You can also use serverId as an alias.
status CHANGE_STATUS only The new billing status. Must be a valid transition from the current status (see lifecycle diagram above).
reason Optional Human-readable explanation for the action. Stored in the audit log for accountability.

CHANGE_EMAIL Action Fields

Field Required Description
action Required Must be "CHANGE_EMAIL"
ownerId Required The guild owner's current email address.
newEmail Required The new email address. Must not already be in use by another user. After success, use this as the ownerId in all future requests.

All actions must also include timestamp (Unix milliseconds, within 5 minutes of server time) and nonce (unique string, at least 16 characters). See the Security Fields section above.

Error Codes Reference

Both partner endpoints return a statusCode field that follows HTTP conventions. Here's what each code means and what to do about it:

Code Meaning What to do
200 Success The action completed. Check the response for details.
201 Created A new resource was created (guild or game server). Store the returned IDs.
206 Missing fields The partnerId or encryptedData was missing from the request.
400 Bad request Decryption failed (wrong key?), or the payload failed validation. Check the message for details.
401 Unauthorized Partner not found, inactive, or no encryption key configured. Check your partnerId.
403 Conflict / Forbidden The guild abbreviation, email, ownerId, or Discord ID is already in use; or the server does not belong to your partner/ownerId.
404 Not found The specified server ID doesn't exist. Double-check the UUID.
409 State conflict The status transition is not allowed (e.g. trying to change a CANCELLED server), or the refund grace period has expired.
500 Server error Something went wrong on our end. Retry after a short delay. If it persists, contact support.

Troubleshooting

"Decryption failed" (400)

This means the server couldn't decrypt your payload. Common causes:

  • You're using the Base64 string directly as the key instead of Base64-decoding it first
  • Your key has been regenerated — ask your account manager for the latest key
  • The byte order is wrong — it must be [IV][ciphertext][tag], not [IV][tag][ciphertext]
  • You're using a 128-bit or 192-bit AES variant instead of 256-bit

"Timestamp expired" or "Timestamp too far in the future" (400)

Your timestamp is more than 5 minutes from the server's clock. Check:

  • Your system clock is accurate (use NTP sync)
  • You're sending Unix milliseconds, not seconds (multiply by 1000)
  • You're generating the timestamp immediately before sending, not caching it

"Nonce must be at least 16 characters" (400)

Your nonce is too short. Use uuid.uuid4().hex (Python), Guid.NewGuid().ToString("N") (.NET), or crypto.randomBytes(16).toString('hex') (Node.js) to generate a 32-character hex string.

"Partner not found" or "Partner inactive" (401)

Double-check your partnerId — this is your unique identifier (e.g. "your-partner-id"), not a UUID. If you're sure it's correct, your partner account may have been deactivated. Contact your Bifrost account manager.

"Invalid state transition" (409)

You're trying to change a server to a status that isn't allowed from its current state. For example, you can't change a CANCELLED server to anything — it's a terminal state. See the billing lifecycle diagram above for valid transitions.

Need Help?

If you're stuck or something isn't working as expected, reach out to your Bifrost account manager. Please include:

  • Your partnerId
  • The full error response (including statusCode and message)
  • The approximate time of the request (so we can check server logs)
  • The language/platform you're using

In partnership with

Brilliant game servers for communities large and small.

Get yours today!

Shrapnelworks Logo

A Shrapnelworks product