Ready for Production?
Learn how to transition from testing to production with proper service account setup.
Service Account Types
| Service Account | Role | Security | Use Case |
|---|---|---|---|
| Client-Side | playerLogin | Safe to expose in apps | Log in existing players with their federated_id |
| Server-Side | playerCreate | Keep secret on server | Create new players from your backend |
Typical Onboarding Flow
1. Quick Testing (What you've been doing)
You copied the Client-Side API key + test player's federated_id → immediately works in your app.
This is perfect for getting started quickly, but not suitable for production with real users.
2. Production Setup
Now it's time to separate concerns:
Your Backend (Server-Side Service Account):
- Use the Server-Side API key to create new players
- This key stays secret on your backend
- Creates players and returns federated_id
Your App (Client-Side Service Account):
- Use the Client-Side API key for player login
- This key is safe to embed in your app
- Logs in players using their federated_id
Making the Transition
Until now, you've used the Client-Side Service Account for testing. Here's how to move to production:
Step 1: Create Players from Backend
Your backend should use the Server-Side Service Account to create players:
- Node.js
- Python
- Go
- Java
- Rust
// Your backend server (Node.js/Express)
const response = await fetch('https://pug.stg.uglabs.app/api/players', {
method: 'POST',
headers: {
'Authorization': `Bearer ${SERVER_SIDE_API_KEY}`, // Server-Side key
'Content-Type': 'application/json'
},
body: JSON.stringify({
external_id: userEmail // Your unique identifier
})
});
const { federated_id } = await response.json();
// Store federated_id in your database
await db.users.create({ email: userEmail, federatedId: federated_id });
import httpx
# Your backend server (FastAPI)
async def create_player(user_email: str) -> str:
async with httpx.AsyncClient() as client:
response = await client.post(
'https://pug.stg.uglabs.app/api/players',
headers={
'Authorization': f'Bearer {SERVER_SIDE_API_KEY}', # Server-Side key
'Content-Type': 'application/json'
},
json={
'external_id': user_email # Your unique identifier
}
)
data = response.json()
federated_id = data['federated_id']
# Store federated_id in your database
await db.users.create(email=user_email, federated_id=federated_id)
return federated_id
package main
import (
"bytes"
"encoding/json"
"net/http"
)
// Your backend server (Go)
type CreatePlayerRequest struct {
ExternalID string `json:"external_id"`
}
type CreatePlayerResponse struct {
FederatedID string `json:"federated_id"`
}
func createPlayer(userEmail string) (string, error) {
reqBody, _ := json.Marshal(CreatePlayerRequest{
ExternalID: userEmail,
})
req, _ := http.NewRequest(
"POST",
"https://pug.stg.uglabs.app/api/players",
bytes.NewBuffer(reqBody),
)
req.Header.Set("Authorization", "Bearer "+SERVER_SIDE_API_KEY)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
var result CreatePlayerResponse
json.NewDecoder(resp.Body).Decode(&result)
// Store federated_id in your database
// db.CreateUser(userEmail, result.FederatedID)
return result.FederatedID, nil
}
import java.net.http.*;
import java.net.URI;
import com.fasterxml.jackson.databind.ObjectMapper;
// Your backend server (Spring Boot)
public class PlayerService {
private static final String API_URL = "https://pug.stg.uglabs.app/api/players";
private static final HttpClient client = HttpClient.newHttpClient();
private static final ObjectMapper mapper = new ObjectMapper();
public String createPlayer(String userEmail) throws Exception {
// Prepare request body
var requestBody = Map.of("external_id", userEmail);
String jsonBody = mapper.writeValueAsString(requestBody);
// Create request
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(API_URL))
.header("Authorization", "Bearer " + SERVER_SIDE_API_KEY)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
// Send request
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
// Parse response
var responseData = mapper.readValue(response.body(), Map.class);
String federatedId = (String) responseData.get("federated_id");
// Store federated_id in your database
// userRepository.save(new User(userEmail, federatedId));
return federatedId;
}
}
use reqwest;
use serde::{Deserialize, Serialize};
#[derive(Serialize)]
struct CreatePlayerRequest {
external_id: String,
}
#[derive(Deserialize)]
struct CreatePlayerResponse {
federated_id: String,
}
// Your backend server (Actix/Rocket)
async fn create_player(user_email: &str) -> Result<String, Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let request_body = CreatePlayerRequest {
external_id: user_email.to_string(),
};
let response = client
.post("https://pug.stg.uglabs.app/api/players")
.header("Authorization", format!("Bearer {}", SERVER_SIDE_API_KEY))
.header("Content-Type", "application/json")
.json(&request_body)
.send()
.await?;
let data: CreatePlayerResponse = response.json().await?;
// Store federated_id in your database
// db.create_user(user_email, &data.federated_id).await?;
Ok(data.federated_id)
}
Step 2: Login from Your App
Your app uses the Client-Side Service Account + the user's federated_id to login:
// Your app (client-side)
const response = await fetch('https://pug.stg.uglabs.app/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
api_key: CLIENT_SIDE_API_KEY, // Safe to embed
federated_id: federatedId // From your backend
})
});
const { access_token } = await response.json();
Complete Production Flow
Security Checklist
Before going to production, verify:
- Server-Side API key is stored securely on your backend (not in client code)
- Client-Side API key is embedded in your app
- Players are created only from your backend using Server-Side key
- Federated IDs are stored securely in your database
- Your app logs in players using Client-Side key + federated_id
- API keys are different for dev/staging/production environments
- You have error handling for player creation and authentication
Next Steps
- Player Onboarding - Complete registration examples
- Authentication (Advanced) - Deep dive into security
- WebSocket Protocol - API documentation